I need to upload a File , process it and then download the processed file within the same POST request.
I didn't find a lot of documentation about Web API covering my requirement, so I've come up with a solution based on different post's found on the net.
My final solution looks like that:
public HttpResponseMessage PostFile(string fileName)
{
try
{
var task = Request.Content.ReadAsStreamAsync();
task.Wait();
var requestStream = task.Result;
var tempFile = System.Web.HttpContext.Current.Server.MapPath(string.Format("~/App_Data/{0}", fileName));
var steam = File.Create(tempFile);
requestStream.CopyTo(steam);
steam.Close();
requestStream.Close();
var modifiedStream = DoStuffToFile(tempFile);
var response = new HttpResponseMessage();
response.Content = new StreamContent(modifiedStream);
response.StatusCode = HttpStatusCode.Created;
return response;
}
catch
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
As I have no experience in Web API, I would like to know if this piece of code is ok,
or if I will run into problems?
Edit:
The code works as expected. I'm just not sure if it can cause any problems or side effects for exmaple because I forgot to do something that might be considered by the Web API. Or is it ok that I post a file and send another one back with the response?
Code is also simplified to keep it compact(no check for duplicate files, no cleanup of old files, etc)
Related
I'm currently in the process of migrating several Azure Function Apps to .NET 6. One of these involves returning various content files via a HTTP request.
Previously (on .NET 3.1) this works fine for both json/text files, and HTML:
var callbackFileLocation = Path.Combine(Helper.GetFunctionPath(), "Files", filename);
var stream = new FileStream(callbackFileLocation, FileMode.Open, FileAccess.Read)
{
Position = 0
};
var okObjectResult = new OkObjectResult(stream);
okObjectResult.ContentTypes.Clear();
if (filename.Contains(".html"))
{
okObjectResult.ContentTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("text/html"));
}
else
{
okObjectResult.ContentTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
}
return okObjectResult;
This doesn't return the same results on .NET Core 6 - you tend to just get given the object type as a name e.g. Microsoft.AspNetCore.Mvc.OkObjectResult or System.IO.FileStream. It's easy enough to fix for the json files, as I can just convert them into text, and make sure the function app is returning that as the payload.
HTML seems trickier - I've tried reading the stream to end, and various methods mentioned here and on other sites, e.g:
public static HttpResponseMessage Run(string filename)
{
var callbackFileLocation = Path.Combine(Helper.GetFunctionPath(), "Files", filename);
var response = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(callbackFileLocation, FileMode.Open);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
Or returning the HTML text within FileContentResult ("application/octet-stream") or ContentResult, e.g:
new ContentResult { Content = content, ContentType = "text/html", StatusCode = 200 };
The closest I've got is the HTML as raw text, but want the HTML rendered in the browser.
Any suggestions? Documentation around this on .NET 6 seems thin...thanks!
Not the best answer (and thanks for the help #Jonas Weinhardt) - but I couldn't find a way to do this using dotnet-isolated process.
It worked fine when moved back to non-isolated. (I guess it's something to do with the new GRPC functions or something like that?)
I have tried to create a simple console application.
We have a call system from 8x8 that provide a web streaming API but their documentation is very limited and nothing in C#.
The api service streams call statuses in near real time and I would like to get that 'stream' and be able to read and process it in realtime if possible. The response or Content Type is 'text/html'. But the actual body of the response can be declared as json - sample below:
{"Interaction":{"attachedData":{"attachedDatum":[{"attachedDataKey":"#pri","attachedDataValue":100},{"attachedDataKey":"callingName","attachedDataValue":999999999999},{"attachedDataKey":"cha","attachedDataValue":99999999999},{"attachedDataKey":"cnt","attachedDataValue":0},{"attachedDataKey":"con","attachedDataValue":0},{"attachedDataKey":"med","attachedDataValue":"T"},{"attachedDataKey":"pho","attachedDataValue":9999999999},{"attachedDataKey":"phoneNum","attachedDataValue":9999999999},{"attachedDataKey":"tok","attachedDataValue":999999999}]},"event":"InteractionCreated","inboundChannelid":9999999999,"interactionEventTS":9999999,"interactionGUID":"int-15b875d0da2-DJOJkDhDsrh3AIaFP8VkICv9t-phone-01-testist","resourceType":0}}
I have seen several posts concerning httpClient and the GetAsync methods but none of these appear to work as they appear to be for calls when a response is made, not something that constantly has a response.
Using fiddler for the call it does not appear to close so the stream is constantly running, so fiddler does not display any data until a separate user or instance connects.
When I use a browser the content is 'streamed' to the page and updates automatically and shows all the content (as above).
The api contains authentication so when another client connects and retrieves data the connected client closes and finally I am able to see the data that was gathering.
This is the code so and does return the big stream when another client connects but ideally I want a real time response and appears to just get stuck in the GETASYNC method:
var response = await client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
string responseString = await responseContent.ReadAsStringAsync();
Console.WriteLine(responseString);
}
Hopefully that's enough information for one of you clever people to help me in my predicament.
I was also having an issue consuming their streaming API and the examples I found that worked with the Twitter and CouchBase streaming API's did not work with 8x8. Both Twitter and CouchBase send line terminators in their pushes so the solution relied on ReadLine to pull in the feed. Since 8x8 does not send terminators you'll need to use ReadBlock or better ReadBlockAsync.
The following code shows how to connect using credentials and consume their feed:
private static async Task StreamAsync(string url, string username, string password)
{
var handler = new HttpClientHandler()
{
Credentials = new NetworkCredential {UserName = username, Password = password},
PreAuthenticate = true
};
// Client can also be singleton
using (var client = new HttpClient(handler))
{
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Connection.Add("keep-alive");
using (var response = await client.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead))
{
using (var body = await response.Content.ReadAsStreamAsync())
{
using (var reader = new StreamReader(body))
{
while (!reader.EndOfStream)
{
var buffer = new char[1024];
await reader.ReadBlockAsync(buffer, 0, buffer.Length);
Console.WriteLine(new string(buffer));
}
}
}
}
}
}
I have recently started working with web api's.
I need to download a file in C# project from a web api, which works fine when I hit the web api using postman's send and download option. Refer to the image, also please check the response in header's tab. This way, I am able to directly download the file to my computer.
I want to do the same from my C# project, I found following two links which shows how to download a file from web api.
https://code.msdn.microsoft.com/HttpClient-Downloading-to-4cc138fd
http://blogs.msdn.com/b/henrikn/archive/2012/02/16/downloading-a-google-map-to-local-file.aspx
I am using the following code in C# project to get the response:
private static async Task FileDownloadAsync()
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Accept", "text/html");
try
{
// _address is exactly same which I use from postman
HttpResponseMessage response = await client.GetAsync(_address);
if (response.IsSuccessStatusCode)
{
}
else
{
}
}
catch (Exception)
{
throw;
}
}
}
However I am not getting the response at all (before I can start to convert the response to a file), please check the error message coming:
What am I missing here, any help would be appreciated.
As the (500s) error says - it's the Server that rejects the request. The only thing I see that could cause an issues is the charset encoding. Yours is the default UTF-8. You could try with other encodings.
Below method uses:
SSL certificate (comment out code for cert, if you don't use it)
Custom api header for additional layer of security (comment out Custom_Header_If_You_Need code, if you don't need that)
EnsureSuccessStatusCode will throw an error, when response is not 200. This error will be caught in and converted to a human readable string format to show on your screen (if you need to). Again, comment it out if you don't need that.
private byte[] DownloadMediaMethod(string mediaId)
{
var cert = new X509Certificate2("Keystore/p12_keystore.p12", "p12_keystore_password");
var handler = new WebRequestHandler { ClientCertificates = { cert } };
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Custom_Header_If_You_Need", "Value_For_Custom_Header");
var httpResponse = client.GetAsync(new Uri($"https://my_api.my_company.com/api/v1/my_media_controller/{mediaId}")).Result;
//Below one line is most relevant to this question, and will address your problem. Other code in this example if just to show the solution as a whole.
var result = httpResponse.Content.ReadAsByteArrayAsync().Result;
try
{
httpResponse.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
if (result == null || result.Length == 0) throw;
using (var ms = new MemoryStream(result))
{
var sr = new StreamReader(ms);
throw new Exception(sr.ReadToEnd(), ex);
}
}
return result;
}
}
Once you have your http response 200, you can use the received bytes[] as under to save them in a file:
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
fs.Write(content, 0, content.Length);
}
Your request header says that you accept HTML responses only. That could be a problem.
Similar to this question, an HttpResponseMessage never completes when returning manually formatted html from Web API. All content is transmitted correctly, but the response doesn't close and the browser load-wheel keeps spinning.
The difference is that I'm not using Application Insights.
public async Task<HttpResponseMessage> GetHtml(string id)
{
var asset = await ...
var builder = new StringBuilder();
builder.Append("<!DOCTYPE HTML>");
// ...
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(builder.ToString(), Encoding.UTF8, "text/html")
};
}
This html is not intended for browser consumption, but for Excel data imports. Just like the browser, Excel can't fetch the data because it doesn't complete.
Any ideas?
UPDATE
Using a custom MediaTypeFormatter works. Documentation here.
return Request.CreateResponse(HttpStatusCode.OK, builder.ToString(), new HTMLFormatter(), new MediaTypeHeaderValue("text/html"));
Not posting as an answer as I can't tell why that is.
UPDATE 2
When removing the repository call to fetch db records and simply returning a mock html document, the http response closes correctly. No exceptions thrown and the db records are retrieved correctly. Dependencies are resolved by Unity.
Hi and thanks for looking!
Background
I am using the Rotativa pdf tool to read a view (html) into a PDF. It works great, but it does not natively offer a way to save the PDF to a file system. Rather, it only returns the file to the user's browser as a result of the action.
Here is what that code looks like:
public ActionResult PrintQuote(FormCollection fc)
{
int revisionId = Int32.Parse(Request.QueryString["RevisionId"]);
var pdf = new ActionAsPdf(
"Quote",
new { revisionId = revisionId })
{
FileName = "Quote--" + revisionId.ToString() + ".pdf",
PageSize = Rotativa.Options.Size.Letter
};
return pdf;
}
This code is calling up another actionresult ("Quote"), converting it's view to a PDF, and then returning the PDF as a file download to the user.
Question
How do I intercept the file stream and save the PDF to my file system. It is perfect that the PDF is sent to the user, but my client also wants the PDF saved to the file system simultaneously.
Any ideas?
Thanks!
Matt
I have the same problem, here's my solution:
You need to basically make an HTTP request to your own URL and save the output as a binary file. Simple, no overload, helper classes, and bloated code.
You'll need this method:
// Returns the results of fetching the requested HTML page.
public static void SaveHttpResponseAsFile(string RequestUrl, string FilePath)
{
try
{
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(RequestUrl);
httpRequest.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";
httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)httpRequest.GetResponse();
}
catch (System.Net.WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
response = (HttpWebResponse)ex.Response;
}
using (Stream responseStream = response.GetResponseStream())
{
Stream FinalStream = responseStream;
if (response.ContentEncoding.ToLower().Contains("gzip"))
FinalStream = new GZipStream(FinalStream, CompressionMode.Decompress);
else if (response.ContentEncoding.ToLower().Contains("deflate"))
FinalStream = new DeflateStream(FinalStream, CompressionMode.Decompress);
using (var fileStream = System.IO.File.Create(FilePath))
{
FinalStream.CopyTo(fileStream);
}
response.Close();
FinalStream.Close();
}
}
catch
{ }
}
Then inside your controller, you call it like this:
SaveHttpResponseAsFile("http://localhost:52515/Management/ViewPDFInvoice/" + ID.ToString(), "C:\\temp\\test.pdf");
And voilĂ ! The file is there on your file system and you can double click and open the PDF, or email it to your users, or whatever you need.
return new Rotativa.ActionAsPdf("ConvertIntoPdf")
{
FileName = "Test.pdf", PageSize = Rotativa.Options.Size.Letter
};
Take a look at the MVC pipeline diagram here:
http://www.simple-talk.com/content/file.ashx?file=6068
The method OnResultExecuted() is called after the ActionResult is rendered.
You can override this method or use an ActionFilter to apply and OnResultExecuted interceptor using an attribute.
Edit:
At the end of this forum thread you will find a reply which gives an example of an ActionFilter which reads (and changes) the response stream of an action. You can then copy the stream to a file, in addition to returning it to your client.
I successfully used Aaron's 'SaveHttpResponseAsFile' method, but I had to alter it, as the currently logged in user's credentials weren't applied (and so it was forwarding to MVC4's login url).
public static void SaveHttpResponseAsFile(System.Web.HttpRequestBase requestBase, string requestUrl, string saveFilePath)
{
try
{
*snip*
httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
httpRequest.Headers.Add(HttpRequestHeader.Cookie, requestBase.Headers["Cookie"]);
*snip*</pre></code>
Then in your calling Controller method, simply add 'Request' into the SaveHttpResponseAsFile call.
You can also do it using Rotativa, which is actually quite easy.
Using Rotativa;
...
byte[] pdfByteArray = Rotativa.WkhtmltopdfDriver.ConvertHtml( "Rotativa", "-q", stringHtmlResult );
File.WriteAllBytes( outputPath, pdfByteArray );
I'm using this in a winforms app, to generate and save the PDFs from Razor Views we also use in our web apps.
I was able to get Eric Brown - Cal 's solution to work, but I needed a small tweak to prevent an error I was getting about the directory not being found.
(Also, looking at the Rotativa code, it looks like the -q switch is already being passed by default, so that might not be necessary, but I didn't change it.)
var bytes = Rotativa.WkhtmltopdfDriver.ConvertHtml(Server.MapPath(#"/Rotativa"), "-q", html);