In this post I describe not 1 but 5! (5!!) possible ways of solving this problem. Most of them have problems of some kind, so pick the one that works best for you. Or find another method and tell me about it in the comments!
1. Manually retrieve incoming Stream from the Request property on the Controller.
This will work for any content type because none of the method arguments need to be extracted from the HTTP Request body, so no Media Type Formatter is invoked.1 2 3 4 5 6 7 | [HttpPut] public HttpResponseMessage AddDocument(string id) { Task<Stream> value = Request.Content.ReadAsStreamAsync(); // Add document to backing store return new HttpResponseMessage(HttpStatusCode.Created); } |
(As all the methods to read the content are asynchronous you would normally change the method declaration to be:
public async Task<HttpResponseMessage> AddDocument(string id)
so that you can use the await keyword to handle the asynchronous calls).
This works! BUT there are some...
Problems with this approach:
Your Controller method needs to contain the logic to extract the Stream content from the incoming HTTP Request. The principle of Separation of Concerns says, in summary, that each unit of functionality in your program (whether that's a method or a class) should do just one thing. Separate tasks should be placed in separate classes/methods. Following Separation of Concerns makes your code more testable and easier to change without breaking everything. I do not recommend this approach.
2. Create a Media Type Formatter to handle every type of content you can think of.
Using the really big list of content types from this post you can create a Media Type Formatter that will handle any of them:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web; namespace WebApplication5 { public class StreamMediaTypeFormatter : MediaTypeFormatter { public StreamMediaTypeFormatter() { this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/h323")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/3gpp2")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/3gpp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-7z-compressed")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/audible")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/aac")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/vnd.audible.aax")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/ac3")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msaccess.addin")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msaccess")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msaccess.cab")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msaccess.runtime")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msaccess.webapplication")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msaccess.ftemplate")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/internet-property-stream")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-bridge-url")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/vnd.dlna.adts")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/postscript")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-aiff")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/aiff")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.adobe.air-application-installer-package+zip")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-mpeg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-application")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-jg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ms-asf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/atom+xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/basic")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-msvideo")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/olescript")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-bcpio")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/bmp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-caf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-office.calx")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-pki.seccat")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-cdf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-x509-ca-cert")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-java-applet")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msclip")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-cmx")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/cis-cod")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-ms-contact")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-cpio")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-mscardfile")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pkix-crl")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-csh")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-director")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-dv")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msdownload")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/dlm")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/msword")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-word.document.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.document")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-word.template.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.template")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-dvi")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("drawing/x-dwf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("message/rfc822")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/etl")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-setext")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/envoy")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.fdf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/fractals")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("x-world/x-vrml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-flv")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/fsharp-script")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/gif")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-ms-group")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-gsm")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-gtar")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-gzip")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-hdf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-hdml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-oleobject")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/winhlp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/mac-binhex40")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/hta")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-component")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/webviewhtml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-icon")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/ief")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-iphone")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-internet-signup")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-ipa")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-ipg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-ipsw")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-ms-iqy")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-ite")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-itlp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-itms")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-itunes-itpc")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ivf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/java-archive")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/liquidmotion")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/pjpeg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-java-jnlp-file")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-javascript")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/jscript")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-latex")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/windows-library+xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-reader")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-la-asf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msmediaview")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/mpeg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/vnd.dlna.mpeg-tts")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-mpegurl")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/m4a")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/m4b")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/m4p")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-m4r")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-m4v")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-macpaint")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-troff-man")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-manifest")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msaccess")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-troff-me")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-shockwave-flash")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/mid")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-smaf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msmoney")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/quicktime")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-sgi-movie")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/mpeg")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/mp4")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-mediapackage")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-project")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-troff-ms")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-miva-compiled")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-mmxp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-netcdf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/oda")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-ms-odc")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.oasis.opendocument.presentation")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/oleobject")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.oasis.opendocument.text")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/onenote")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/opensearchdescription+xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pkcs10")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-pkcs12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-pkcs7-certificates")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pkcs7-mime")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-pkcs7-certreqresp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pkcs7-signature")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-portable-bitmap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-podcast")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/pict")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pdf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-portable-graymap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-pki.pko")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/scpls")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-perfmon")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-portable-anymap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-powerpoint")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-powerpoint.template.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.presentationml.template")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-powerpoint.addin.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-portable-pixmap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-powerpoint.slideshow.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.presentationml.slideshow")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-powerpoint.presentation.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.presentationml.presentation")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/pics-rules")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/powershell")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-mspublisher")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-html-insertion")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-quicktime")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-quicktimeplayer")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-pn-realaudio")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-cmu-raster")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/rat-file")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/vnd.rn-realflash")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-rgb")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.rn-realmedia")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.rn-rn_music_package")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-troff")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-pn-realaudio-plugin")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-ms-rqy")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/rtf")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/richtext")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-safari-safariextz")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msschedule")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/scriptlet")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-sd2")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/sdp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/windows-search-connector+xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/set-payment-initiation")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/set-registration-initiation")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-sgimb")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/sgml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-sh")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-shar")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-stuffit")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-powerpoint.slide.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.presentationml.slide")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-excel")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-license")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-smd")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/futuresplash")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-wais-source")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/streamingmedia")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-pki.certstore")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-pki.stl")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-sv4cpio")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-sv4crc")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-tar")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-tcl")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-tex")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-texinfo")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-compressed")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-officetheme")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/tiff")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msterminal")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/tab-separated-values")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/iuls")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ustar")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/vbscript")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/x-vcard")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-visio.viewer")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.visio")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/ms-vsi")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vsix")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-vsto")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/wav")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-ms-wax")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/vnd.wap.wbmp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-works")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/vnd.ms-photo")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-safari-webarchive")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/wlmoviemaker")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-wlpg-detect")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-wlpg3-detect")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ms-wm")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("audio/x-ms-wma")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-wmd")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msmetafile")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/vnd.wap.wml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.wap.wmlc")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/vnd.wap.wmlscript")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.wap.wmlscriptc")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ms-wmp")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ms-wmv")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ms-wmx")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-wmz")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-wpl")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-mswrite")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("video/x-ms-wvx")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/directx")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xaml+xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-silverlight-app")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-ms-xbap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-xbitmap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xhtml+xml")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-excel.addin.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-excel.sheet.binary.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-excel.sheet.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-excel.template.macroenabled.12")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.template")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-xpixmap")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.ms-xpsdocument")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/x-xwindowdump")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-compress")); this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-zip-compressed")); } public override bool CanReadType(Type type) { return typeof(Stream) == type; } public override bool CanWriteType(Type type) { return false; } public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) { return Task.FromResult((object)readStream); } public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, System.Threading.CancellationToken cancellationToken) { return ReadFromStreamAsync(type, readStream, content, formatterLogger); } } } |
This works! BUT there are some fairly obvious...
Problems with this approach:
My programmer's instincts tell me that hard coding a list in this way is not good. What if my customers start using a new MIME type that isn't on my list? But the list is fairly comprehensive so maybe this is OK in practice.
3. Use a Model Binder instead of a Media Type Formatter.
In my last post I said that Media Type Formatters were what Web API had instead of MVC's Model Binders. That's sort of true because Media Type Formatters are what you would normally use to parse incoming POSTed or PUT content, but you can also use a Web API Model Binder (NOTE: different from a MVC Model Binder!). Below is an example of a Model Binder that will do exactly this:1 2 3 4 5 6 7 8 9 10 11 12 13 14 | using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; namespace WebApplication5 { public class StreamModelBinder : IModelBinder { public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { bindingContext.Model = actionContext.Request.Content.ReadAsStreamAsync().Result; return true; } } } |
You also need to add a ModelBinder attribute to the controller method argument, like this:
1 2 3 4 5 6 7 | [HttpPut] public HttpResponseMessage AddDocument(string id, [ModelBinder(typeof(StreamModelBinder))]Stream value) { // Add document to backing store return new HttpResponseMessage(HttpStatusCode.Created); } |
This works! BUT there are some...
Problems with this approach:
This is a little bit of an abuse of the ModelBinder system, which is meant to be used to parse complex types from url and querystring portions. But the nastiest thing about it is that we have to call an asynchronous method, ReadAsStreamAsync, in a synchronous manner. I don't get a thread deadlock (tested it) but I'm not sure how well this would perform under heavy load as the Task Scheduler will be using background threads to read the incoming stream.4. Use MVC instead of Web API.
If Web API's strategy of breaking incoming content down by content type is becoming a pain then don't use it; use MVC instead! I can create the following controller:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.IO; using System.Net; using System.Web; using System.Web.Mvc; namespace WebApplication5.Controllers { public class ValuesMvcController : Controller { [HttpPut] public ActionResult AddDocument(string id, [ModelBinder(typeof(StreamMvcModelBinder))] Stream value) { // Add document to backing store return new HttpStatusCodeResult(HttpStatusCode.Created); } } } |
And define the Model Binder (an MVC Model Binder this time) for the argument like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | using System.Web; using System.Web.Mvc; namespace WebApplication5 { public class StreamMvcModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return controllerContext.HttpContext.Request.InputStream; } } } |
All that remains now is to sort out the routing to ensure that my new controller handles the requests that the Web API controller was handling. My original Web API controller is the ValuesController that Visual Studio creates in a vanilla ASP.NET Web API project. The Url for doing a PUT to this was
/api/values/1
If I want to use a different controller to handle the PUT requests but keep the same Url schema then I need to add some constraints to the Web API routes to stop them handling PUT requests:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System.Net.Http; using System.Web.Http; using System.Web.Http.Routing; namespace WebApplication5 { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API routes config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: new {httpMethod = new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Delete, HttpMethod.Post) } ); } } } |
I also need to add a fairly custom route for my new MVC controller to ensure that it handles PUT requests to Urls of the form /api/values/n:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace WebApplication5 { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute(name: "ValuesRoute", url: "api/values/{id}", defaults: new { controller = "ValuesMvc", action = "AddDocument", id = UrlParameter.Optional }, constraints: new {httpMethod = new HttpMethodConstraint("PUT")} ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } } |
It works, and I don't have to call any asynchronous methods in a synchronous fashion in the Model Binder as I had to do with the Web API Model Binder. I'm retaining separation of concerns; I'm slightly bending the purpose of a Model Binder by making it read the whole input stream but I don't think that will cause any problems. BUT it's really ugly having to split the functionality for one area of the system between two controllers like this and could well cause confusion to other developers working on the code. So I don't really recommend this approach.
5. Create your own interface and implementation to abstract out the task of retrieving the content from the incoming HTTP Request
"All programs in computer science can be solved by another level of indirection", as a great programmer said.When the Web API provides us with so many extensibility points it may seem greedy to create one more, but I'm not really happy with any of the approaches I've shown so far, so this is the one I've gone with. I create a new interface, IRequestContextProvider, that needs to be implemented by objects that are responsible for retrieving the incoming HTTP stream:
1 2 3 4 5 6 7 8 9 10 11 | using System.IO; using System.Threading.Tasks; using System.Web.Http; namespace WebApplication5 { public interface IRequestContextProvider { Task<Stream> GetInputStream(ApiController controller); } } |
My standard implementation of this is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | using System.IO; using System.Threading.Tasks; using System.Web.Http; namespace WebApplication5 { public class WebRequestContextProvider : IRequestContextProvider { public Task<Stream> GetInputStream(ApiController controller) { return controller.Request.Content.ReadAsStreamAsync(); } } } |
(For my unit tests I will create a Mock using either a separate implementation or a mocking framework like Moq).
My controller class now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | using System.IO; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.ModelBinding; namespace WebApplication5.Controllers { public class ValuesController : ApiController { private IRequestContextProvider requestContextProvider; public ValuesController(IRequestContextProvider requestContextProvider) { this.requestContextProvider = requestContextProvider; } [HttpPut] public async Task<HttpResponseMessage> AddDocument(string id) { Stream value = await requestContextProvider.GetInputStream(this); // Add document to backing store return new HttpResponseMessage(HttpStatusCode.Created); } // other methods here } } |
And I inject the standard implementation of IRequestContextProvider into my controller using Ninject, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | using Ninject; using System; using System.Collections.Generic; using System.Web.Http.Dependencies; namespace WebApplication5 { public class NinjectDependencyResolver : IDependencyResolver { private IKernel Kernel; public NinjectDependencyResolver() { Kernel = new StandardKernel(); Kernel.Bind<IRequestContextProvider>().To<WebRequestContextProvider>().InSingletonScope(); } public IDependencyScope BeginScope() { return this; } public object GetService(Type serviceType) { return Kernel.TryGet(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return Kernel.GetAll(serviceType); } public void Dispose() { } } } using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace WebApplication5 { public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(); } } } |
The only thing you could have against this approach is that it's the most complicated. But I like it and I'm using it.
Oh my God!
ReplyDeleteI can't believe no one has commented on this article!
I have searched for days for a solution to my problem of an API Web 2 action accepting a RESTful POST containing the data in the body (as opposed to a form post), with a Content-Type other than the defaults.
Everywhere I looked, everybody else was either accepting simple JSON data, and wanting to know how to RETURN data using a MediaTypeFomatter, or they had control over the input, and just needed to prepend a "=" to the incoming data, but nobody, and I mean NOBODY, seemed to need to know how to ACCEPT a non-configurable, non-JSON/XML data post.
I was pulling my hair out asking isn't ANYBODY writing APIs where they're required to accept data from a non-configurable source, such as a third-party customer, client or partner?
In my case, I'm re-purposing an existing Web API app to accept a restful post from a vendor that is non-configurable...Unless of course, we pay an exorbitant fee for them to make a 30-second change to their post.
I had almost given up hope, and was preparing to either kludge a MVC controller onto the app, or write a separate MVC app to re-route the vendor's post.
But now I don't have to do either. :)
Thanks for this great post!
Tony