Unsupported Media Types when POST to web api - asp.net

Here is the client :
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost/MP.Business.Implementation.FaceAPI/");
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
using (var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress + "api/Recognition/Recognize"))
{
request.Content = new ByteArrayContent(pic);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
await client.PostAsync(request.RequestUri, request.Content);
}
}
and the server :
[System.Web.Http.HttpPost]
public string Recognize(byte[] img)
{
//do someth with the byte []
}
I am getting error:
415 Unsupported Media Type
all the time - The request entity's media type 'application/octet-stream' is not supported for this resource. What can i do about it? I've found some answered threads here , but it didnt help.

While byte[] would be a great way to represent application/octet-stream data, this is not the case by default in Web API.
My workaround is in ASP.NET Core 1.1 - the details may be different in other variants.
In your controller method, remove the img parameter. Instead, refer to the Request.Body, which is a Stream. e.g. to save to a file:
using (var stream = new FileStream(someLocalPath, FileMode.Create))
{
Request.Body.CopyTo(stream);
}
The situation is similar for returning binary data from a GET controller method. If you make the return type byte[] then it is formatted with base64! This makes it significantly larger. Modern browsers are perfectly capable of handling raw binary data so this is no longer a sensible default.
Fortunately there is a Response.Body https://github.com/danielearwicker/ByteArrayFormatters:
Response.ContentType = "application/octet-stream";
Response.Body.Write(myArray, 0, myArray.Length);
Make the return type of your controller method void.
UPDATE
I've created a nuget package that enables direct use of byte[] in controller methods. See: https://github.com/danielearwicker/ByteArrayFormatters

Related

Angular / Asp.Net Web Api request binary data

I have an Angular 4 application which consumes an Asp.Net Web Api, and I want the Api to return a binary file. The Api route seems to be working correctly - I tested it using a rest console and the response is as expected. However, when trying to use the same route in the Angular app, the request sends but returns an error. I can see with the C# debugger that the request is executing completely and doesn't fail on the server. Here's the error in the JS console:
This error occurs on all browsers tested (Chrome, Firefox, IE, Edge, Safari).
Here's the server side code:
[Route("api/getfile")]
public IHttpActionResult GetFile()
{
byte[] file = <file generator code>;
System.Net.Http.HttpResponseMessage responseMessage = new System.Net.Http.HttpResponseMessage
{
Content = new System.Net.Http.StreamContent(new System.IO.MemoryStream(file))
};
responseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
responseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMessage.Content.Headers.ContentDisposition.FileName = "file.pdf";
return this.ResponseMessage(responseMessage);
}
And here's the Angular code:
let headers = new Headers({
"Accept": "application/octet-stream",
"X-Requested-With": "XMLHttpRequest",
"Authorization": `Bearer ${token}`
});
let opts = new RequestOptions({headers = headers});
opts.responseType = ResponseContentType.Blob;
// Uses old #angular/http, not HttpClient
this.http
.get(`${apiUrl}/getfile`, opts)
.map(res => res.blob())
.catch(err => handleError(err));
EDIT: I tried using a plain XMLHttpRequest instead of Angular's Http service and it works. What do I need to do to get this to work with Angular?
EDIT 2: It works if I fetch an actual file on the file system that's accessible using the same host that the Angular app is running on. The Angular app is on localhost:8080, while the api is on a different port. If I expose a file on localhost:8080 (e.g., in the build folder) than I can fetch that file. This makes me wonder if it's a security issue, or maybe has to do with the headers or the way Web Api returns the binary data.
On your Api that will return your PDF
FileContentResult result;
if(!string.IsNullOrEmpty(fileName))
{
string absoluteFileName = Path.Combine(pathToFile, fileName);
byte[] fileContents = System.IO.File.ReadAllBytes(absoluteFileName);
result = new FileContentResult(fileContents, "application/pdf");
}
And then on Angular:
downloadFile(api: string) {
window.open(this.endPoint + api);
}
Try the old Way:
FileInfo fileInfo = New FileInfo(filePath)
Response.Clear()
Response.ClearHeaders()
Response.ClearContent()
Response.AddHeader("Content-Disposition", "attachment;filename=" + fileInfo.Name)
Response.AddHeader("Content-Type", "application/pdf")
Response.ContentType = "application/pdf"
Response.AddHeader("Content-Length", fileInfo.Length.ToString())
Response.TransmitFile(fileInfo.FullName)
Response.Flush()
Response.End()
This is for an image that I was keeping in a blob column in a db, but process should be similar. I ended up doing something like this back in Angular 4 (although this was 4.3+ which means HttpClient, not Http) to handle downloading files on clicking a button:
public downloadImage(id: number, imageName: string, imageType: string) {
this.http.get(urlToApiHere, { responseType: 'blob' }).subscribe((image: Blob) => {
if (isPlatformBrowser(this.platformId)) {
let a = window.document.createElement("a");
document.body.appendChild(a);
let blobUrl = window.URL.createObjectURL(image);
a.href = blobUrl;
a.download = imageName;
a.click();
window.URL.revokeObjectURL(blobUrl);
document.body.removeChild(a);
}
})
}
This API is .Net Core, but should be similar in .Net MVC, I believe:
[HttpGet]
public FileResult DisplayLineItemImage(int lineItemID)
{
using (var db = new Context())
{
var image = //retrieve blob binary, type, and filename here
if (image.Image == null)
{
//throw error
}
return File(image.Image, image.Type, image.Name);
}
}
The second answer by Crying Freeman, using Response directly, does work, but it bypasses Owin's processing and would mean having to manually implement things like CORS or anything else normally handled using CORS.
I found another solution, to use a custom formatter to allow returning a byte array from the controller method. This is also nicer because I don't need to set any headers manually, not even Content-Type.

Set default Media Formatter for a WebAPI action

I have implemented a custom media formatter and it works great when the client specifically requests "csv" format.
I have tested my api controller with this code:
HttpClient client = new HttpClient();
// Add the Accept header
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/csv"));
However, when I open the same URL from a web browser it returns JSON not CSV. This is probably due to standard ASP.NET WebAPI configuration that sets JSON as the default media formatter unless otherwise specified by the caller. I want this default behavior on every other web service I have but NOT on this single operation that returns CSV. I want the default media handler to be the CSV handler that I implemented. How do I configure the Controller's endpoint such that it returns CSV by default and only returns JSON/XML if requested by the client?
Which version of Web API are you using?
If you are using 5.0 version, you could use the new IHttpActionResult based logic like below:
public IHttpActionResult Get()
{
MyData someData = new MyData();
// creating a new list here as I would like CSVFormatter to come first. This way the DefaultContentNegotiator
// will behave as before where it can consider CSVFormatter to be the default one.
List<MediaTypeFormatter> respFormatters = new List<MediaTypeFormatter>();
respFormatters.Add(new MyCsvFormatter());
respFormatters.AddRange(Configuration.Formatters);
return new NegotiatedContentResult<MyData>(HttpStatusCode.OK, someData,
Configuration.Services.GetContentNegotiator(), Request, respFormatters);
}
If you are using 4.0 version of Web API, then you could the following:
public HttpResponseMessage Get()
{
MyData someData = new MyData();
HttpResponseMessage response = new HttpResponseMessage();
List<MediaTypeFormatter> respFormatters = new List<MediaTypeFormatter>();
respFormatters.Add(new MyCsvFormatter());
respFormatters.AddRange(Configuration.Formatters);
IContentNegotiator negotiator = Configuration.Services.GetContentNegotiator();
ContentNegotiationResult negotiationResult = negotiator.Negotiate(typeof(MyData), Request, respFormatters);
if (negotiationResult.Formatter == null)
{
response.StatusCode = HttpStatusCode.NotAcceptable;
return response;
}
response.Content = new ObjectContent<MyData>(someData, negotiationResult.Formatter, negotiationResult.MediaType);
return response;
}

c# how to get a stream processed by httpResponse.BinaryWrite

I have the following method that writes a stream in a HttpResponse object.
public HttpResponse ShowPDF(Stream stream)
{
MemoryStream memoryStream = (MemoryStream) stream;
httpResponse.Clear();
httpResponse.Buffer = true;
httpResponse.ContentType = "application/pdf";
httpResponse.BinaryWrite(memoryStream.ToArray());
httpResponse.End();
return httpResponse;
}
In order to test it, I need to recover the processed stream.
Is there someway to read the stream from the httpResponse object?
I have two ideas... one to mock the HttpResponse, and the other is to simulate a web server.
1. Mocking HttpResponse
I wrote this before I knew which mocking framework you used. Here's how you could test your method using TypeMock.
This assumes that you pass your httpResponse variable to the method, changing the method as follows:
public void ShowPDF(Stream stream, HttpResponse httpResponse)
Of course you would change this to passing it to a property on your Page object instead, if it is a member of your Page class.
And here's an example of how you could test using a fake HttpResponse:
internal void TestPDF()
{
FileStream fileStream = new FileStream("C:\\deleteme\\The Mischievous Nerd's Guide to World Domination.pdf", FileMode.Open);
MemoryStream memoryStream = new MemoryStream();
try
{
memoryStream.SetLength(fileStream.Length);
fileStream.Read(memoryStream.GetBuffer(), 0, (int)fileStream.Length);
memoryStream.Flush();
fileStream.Close();
byte[] buffer = null;
var fakeHttpResponse = Isolate.Fake.Instance<HttpResponse>(Members.ReturnRecursiveFakes);
Isolate.WhenCalled(() => fakeHttpResponse.BinaryWrite(null)).DoInstead((context) => { buffer = (byte[])context.Parameters[0]; });
ShowPDF(memoryStream, fakeHttpResponse);
if (buffer == null)
throw new Exception("It didn't write!");
}
finally
{
memoryStream.Close();
}
}
2. Simulate a Web Server
Perhaps you can do this by simulating a web server. It might sound crazy, but it doesn't look like it's that much code. Here are a couple of links about running Web Forms outside of IIS.
Can I run a ASPX and grep the result without making HTTP request?
http://msdn.microsoft.com/en-us/magazine/cc163879.aspx

Returning binary file from controller in ASP.NET Web API

I'm working on a web service using ASP.NET MVC's new WebAPI that will serve up binary files, mostly .cab and .exe files.
The following controller method seems to work, meaning that it returns a file, but it's setting the content type to application/json:
public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = #"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}
Is there a better way to do this?
Try using a simple HttpResponseMessage with its Content property set to a StreamContent:
// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;
public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = #"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
A few things to note about the stream used:
You must not call stream.Dispose(), since Web API still needs to be able to access it when it processes the controller method's result to send data back to the client. Therefore, do not use a using (var stream = …) block. Web API will dispose the stream for you.
Make sure that the stream has its current position set to 0 (i.e. the beginning of the stream's data). In the above example, this is a given since you've only just opened the file. However, in other scenarios (such as when you first write some binary data to a MemoryStream), make sure to stream.Seek(0, SeekOrigin.Begin); or set stream.Position = 0;
With file streams, explicitly specifying FileAccess.Read permission can help prevent access rights issues on web servers; IIS application pool accounts are often given only read / list / execute access rights to the wwwroot.
For Web API 2, you can implement IHttpActionResult. Here's mine:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;
public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");
_filePath = filePath;
_contentType = contentType;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return Task.FromResult(response);
}
}
Then something like this in your controller:
[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);
return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}
And here's one way you can tell IIS to ignore requests with an extension so that the request will make it to the controller:
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
For those using .NET Core:
You can make use of the IActionResult interface in an API controller method, like so.
[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);
return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}
This example is simplified, but should get the point across. In .NET Core this process is so much simpler than in previous versions of .NET - i.e. no setting response type, content, headers, etc.
Also, of course the MIME type for the file and the extension will depend on individual needs.
Reference: SO Post Answer by #NKosi
While the suggested solution works fine, there is another way to return a byte array from the controller, with response stream properly formatted :
In the request, set header "Accept: application/octet-stream".
Server-side, add a media type formatter to support this mime type.
Unfortunately, WebApi does not include any formatter for "application/octet-stream". There is an implementation here on GitHub: BinaryMediaTypeFormatter (there are minor adaptations to make it work for webapi 2, method signatures changed).
You can add this formatter into your global config :
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
WebApi should now use BinaryMediaTypeFormatter if the request specifies the correct Accept header.
I prefer this solution because an action controller returning byte[] is more comfortable to test. Though, the other solution allows you more control if you want to return another content-type than "application/octet-stream" (for example "image/gif").
For anyone having the problem of the API being called more than once while downloading a fairly large file using the method in the accepted answer, please set response buffering to true
System.Web.HttpContext.Current.Response.Buffer = true;
This makes sure that the entire binary content is buffered on the server side before it is sent to the client. Otherwise you will see multiple request being sent to the controller and if you do not handle it properly, the file will become corrupt.
The overload that you're using sets the enumeration of serialization formatters. You need to specify the content type explicitly like:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
You could try
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
You can try the following code snippet
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Hope it will work for you.

How do I make a simple post to Twitter via ASP.NET (VB, preferably)?

I don't want to do anything fancy on Twitter except post to it via my site once a day. I have searched around a bit and there are all sorts of super-complex ways to do every little thing that Twitter does, but there seems to be little documentation on how to do the simplest thing, which is make a post!
Does anyone know how to do this? Or can you at least point me in the right direction? I don't need full wrappers or anything (http://apiwiki.twitter.com/Libraries#C/NET), just one simple function that will post to Twitter.
Thanks!
This is the easiest implementation ever. Up and running in under 2 minutes: Twitterizer
Its fairly simple; you just need to post an xml file to a web page using webrequest.create. This example is close (assumes you have the xml for the message in another place and just pass it into twitterxml variable as a string. The url might not be the right one; found it on this [page][1] which defines the interface
WebRequest req = null;
WebResponse rsp = null;
try
{
string twitterXML = "xml as string";
string uri = "http://twitter.com/statuses/update.format";
req = WebRequest.Create(uri);
//req.Proxy = WebProxy.GetDefaultProxy(); // Enable if using proxy
req.Method = "POST"; // Post method
req.ContentType = "text/xml"; // content type
// Wrap the request stream with a text-based writer
StreamWriter writer = new StreamWriter(req.GetRequestStream());
// Write the XML text into the stream
writer.WriteLine(twitterXML);
writer.Close();
// Send the data to the webserver
rsp = req.GetResponse();
}
[1]: http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses update
There are a couple different ways of doing this, they vary depending on the tools you want to use and have access to. Option 1 will work right out of the box, but the coding can be complicated. Option 3 you will have to download tools for, but once there installed and loaded you should be able to consume the twitter api very quickly.
Use WebRequest.Create to create/send messages to remote endpoints
Use WCF, create a mirror endpoint and access the twitter api using client only endpoint.
Use the WCF REST Starter Kit Preview 2, which has a new class called the HttpClient. I would have to recommend this technique if you can. Here is a great video Consuming a REST Twitter Feed in under 3 minutes.
Here is a sample of using the WCF REST Starter Kit's HttpClient:
public void CreateFriendship(string friend)
{
using (var client = new HttpClient())
{
var url = string.Format("http://www.twitter.com/friendships/create/{0}.xml?follow=true", friend);
client.Post(url)
.CheckForTwitterError()
.EnsureStatusIs(HttpStatusCode.OK);
}
}
Add a comment if you'd like more info about a particular method.
Update:
For Option #1 see this question: Remote HTTP Post with C#
There are a few ways of doing this, you can check out http://restfor.me/twitter and it will give you the code from RESTful documentation.
Essentially making any authenticated call you can follow this logic:
///
/// Executes an HTTP POST command and retrives the information.
/// This function will automatically include a "source" parameter if the "Source" property is set.
///
/// The URL to perform the POST operation
/// The username to use with the request
/// The password to use with the request
/// The data to post
/// The response of the request, or null if we got 404 or nothing.
protected string ExecutePostCommand(string url, string userName, string password, string data) {
WebRequest request = WebRequest.Create(url);
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) {
request.Credentials = new NetworkCredential(userName, password);
byte[] bytes = Encoding.UTF8.GetBytes(data);
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream()) {
requestStream.Write(bytes, 0, bytes.Length);
using (WebResponse response = request.GetResponse()) {
using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
return reader.ReadToEnd();
}
}
}
}
return null;
}

Resources