I have a function that returns a Stream with data of a file being downloaded using HttpContent that is the content of a HttpResponseMessage (using Owin)
For those interested I'll add the code at the end.
The interface to get the stream is:
async Task<Stream> DownloadLogFileAsync(...);
The caller of this function wants to create a file containing the data of this Stream. According to StackOverFlow: How do I save a stream to File I should use Stream.CopyTo to save the contents of the Stream in a file. Something like this:
(simplified: not using CancellationToken)
using(Stream downloadStream = await DownloadLogFileAsync(...) )
{
using (var fileStream = System.IO.File.Create(fullFileName))
{
await downloadStream.CopyToAsync(fileStream);
}
}
Question:
Would it improve performance if the FileStream has the same buffer size as the downloadStream? How do I get the buffer size of the download stream?
End of Question
Not related to the question, only for those interested in the OWIN / ASP file download:
I have a WCF service with a function that returns data. Creation of this data takes a considerable amount of time. The size of the returned data might be huge. Therefore it is decided to split this function into two functions:
Request creation of the file. Return a unique file handle
Request a stream containing the data of the created file.
Of course my WCF service needs proper functions to cancel creation, delete the created file and do some cleanup if the file is getting old and client forgot to request deletion.
All functions can be done using WCF. The only one that needs OWIN / ASP is the request for the file stream.
class OwinFileClient
{
private const string routePrefix = "SipLoggerFile/";
private readonly WebApiClient webApiClient = new WebApiClient() {...}
// Function to download the created file:
public async Task<Stream> DownloadSipLogFileAsync(Guid fileHandle)
{
var httpClient = this.webApiClient.CreateHttpClient();
string requestStreamFile = routePrefix + #"DownloadFile?fileHandle="
+ fileHandle.ToString("N");
var response = await httpClient.GetAsync(requestStreamFile)
.ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
string content = await response
.Content.ReadAsStringAsync()
.ConfigureAwait(false);
throw new System.Net.Http.HttpRequestException(
$"{response.StatusCode} [{(int)response.StatusCode}]: {content}");
}
// if here: success: deserialize the data
return await response.Content.ReadAsStreamAsync()
.ConfigureAwait(false);
}
}
And the WebApiClient:
class WebApiClient
{
public Uri baseAddress { get; set; }
public TimeSpan Timeout { get; set; }
public ICredentials Credentials { get; set; }
public IWebProxy Proxy { get; set; }
public HttpClient CreateHttpClient()
{
return new HttpClient(CreateHttpMessageHandler())
{
BaseAddress = this.baseAddress,
Timeout = this.Timeout,
};
}
private HttpMessageHandler CreateHttpMessageHandler()
{
return new HttpClientHandler()
{
AutomaticDecompression = System.Net.DecompressionMethods.GZip
| System.Net.DecompressionMethods.Deflate,
Credentials = this.Credentials,
PreAuthenticate = this.Credentials != null,
Proxy = this.Proxy,
UseProxy = this.Proxy != null,
};
}
Related
Currently my webAPI has the following POST endpoint:
public async Task<ActionResult<string>> AddUserImage([FromRoute] string userId, [FromHeader] bool doNotOverwrite, [FromBody] byte[] content, CancellationToken ct)
My goal is to send an image file to the endpoint. However, I cannot find a correct way to send an octect-stream or ByteArrayContent or some other type over the internet. All attempts end in an HTTP 415.
This is my best attempt to send the image over the internet:
public async Task<bool> AddOrReplaceImage(string id, string endpoint, byte[] imgBinary)
{
if (imgBinary is null) throw new ArgumentNullException(nameof(imgBinary));
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Add("doNotOverwrite", "false");
request.Content = JsonContent.Create(imgBinary);
// I also tried: request.Content = new ByteArrayContent(imgBinary);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // Does not seem to change a thing
var apiResult = await new HttpClient().SendAsync(request); // Returns 415
return apiResult.IsSuccessStatusCode;
}
I doubt both the parameters of the endpoint and the way I send the HTTP request. How can I simply receive and send an image over the internet?
Frist Solution :- Which worked in my case.
You can try [FromForm] and IFormFile Like this :-
If controller is annotated with [ApiController] then[FromXxx] is required. For normal view controllers it can be left.
public class PhotoDetails
{
public string id {get;set;}
public string endpoint {get;set;}
public IFormFile photo {get;set;}
}
public async Task<ActionResult<string>> AddUserImage([FromForm] PhotoDetails photoDetails, CancellationToken ct)
I tried this in .net core and it worked but i needed array of files so i used [FromForm] and IFormFile[] and sending from angular.
Second Solution :-
I tried replicate question scenario with question code.
and then changed the implementation and it worked. Please find the below
code
PhotoDetails photopara = new PhotoDetails();
photopara.id = id;
photopara.endpoint = endpoint;
photopara.photo = imgdata;
string json = JsonConvert.SerializeObject(photopara);
var stringContent = new StringContent(json, Encoding.UTF8, "application/json");
using (var client = new HttpClient())
{
var response = await client.PostAsync("http://localhost:57460/WeatherForecast", stringContent);
if (!response.IsSuccessStatusCode)
{
return null;
}
return (await response.Content.ReadAsStreamAsync()).ToString();
}
public class PhotoDetails
{
public string id {get;set;}
public string endpoint {get;set;}
public byte[] photo {get;set;}
}
In this solution, I changed IformFile to byte[] in photodetail class because httpresponsemessage creating problem.
Get Image or byte array in Post Method
Please try this without json serialization
using (var client = new HttpClient())
using (var formData = new MultipartFormDataContent())
{
formData.Add(idContent, "id", "param1");
formData.Add(endpointContent, "endpoint", "file1");
formData.Add(bytesContent, "photo", "file2");
var response = await client.PostAsync("http://localhost:57460/WeatherForecast", formData);
if (!response.IsSuccessStatusCode)
{
return null;
}
return (await response.Content.ReadAsStreamAsync()).ToString();
}
public async Task<ActionResult<int>> AddUserImage([FromForm] PhotoDetails photo, CancellationToken ct)
{
// logic
}
Still Not working then You can try the below link also
Send Byte Array using httpclient
I created a .NET 5 REST Api. I can easily upload files from swagger. That is working fine. When debugging, I can see that the byte array is not empty. Here is the Controller method:
[Route("api/[controller]")]
[ApiController]
public class ImageController : ControllerBase
{
// POST api/<ImageController>
[HttpPost]
public void Post([FromForm] UserModel info)
{
var memoryStream = new MemoryStream();
info.Avatar.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
}
}
This is the UserModel:
public class UserModel
{
[FromForm(Name = "avatar")]
public IFormFile Avatar { get; set; }
[FromForm(Name = "name")]
public string Name { get; set; }
}
I also tried to upload a file programmatically. This is not entirely working. When putting breakpoints in the controller method, I see that the byte array is empty. So the call itself is working but the data is not entering.
Here is the source code of the .NET 5 Console application to upload files.
As explained, this does something useful as it really calls the REST API which I can see by putting breakpoints in the controller method. However, my controller method does not get any data. The byte array is empty.
private static async Task TryUpload()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:5000");
string filePath = "C:\\Users\\daan1982\\Pictures\\RiderStart.png";
var fileStream = File.Create(filePath);
using (var content =
new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))
{
content.Add(new StreamContent(fileStream), "avatar", "RiderStart.png");
var result = await client.PostAsync("/api/Image", content);
var request = result.RequestMessage;
}
}
}
static async Task Main(string[] args)
{
await TryUpload();
Console.WriteLine("Hello World!");
}
As I named the content "avatar" in the upload and also in the request model, this should work fine. However, it does work but not fine as the byte array is always empty.
What am I doing wrong? And how can I fix this?
File.Create "creates or overwrites a file in the specified path."
You probably want File.OpenRead.
That's how it worked for me.
static async Task Main(string[] args)
{
await TryUpload();
}
private const string Boundary = "EAD567A8E8524B2FAC2E0628ABB6DF6E";
private static readonly HttpClient HttpClient = new()
{
BaseAddress = new Uri("https://localhost:5001/")
};
private static async Task TryUpload()
{
var requestContent = new MultipartFormDataContent(Boundary);
requestContent.Headers.Remove("Content-Type");
requestContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary={Boundary}");
var fileContent = await File.ReadAllBytesAsync(#"<path to file\Unbenannt.PNG");
var byteArrayContent = new ByteArrayContent(fileContent);
byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
requestContent.Add(byteArrayContent, "avatar", "Unbenannt.PNG");
var postResponse = await HttpClient.PostAsync("/api/Image", requestContent);
}
I have an API endpoint that returns file as attachment. for example if I access www.myfileservice.com/api/files/download/123 I could download the file directly. My requirement is to use this endpoint in another ASP.Net MVC project. So if the user hits www.mymvcapplication.com/File/DownloadDocument/123 it should also download the same file. Internally the action method should call the file service API and return the result as it is. This is the code I am using:
FileController.cs:
public HttpResponseMessage DownloadDocument(int Id)
{
return new DocumentClient().DownloadDocument(Id);
}
DocumentClient.cs:
public class DocumentClient
{
private string documentServiceURL = string.Empty;
private static string downloadDocumentUri = "api/files/download/";
protected HttpClient documentClient = null;
public DocumentClient()
{
documentServiceURL = "www.myfileservice.com";
documentClient = new HttpClient();
documentClient.BaseAddress = new Uri(documentServiceURL);
}
public HttpResponseMessage DownloadDocument(int Id)
{
return documentClient.GetAsync(String.Format("{0}/{1}", downloadDocumentUri, Id)).Result;
}
}
The code above is not giving any error but only printing the response in browser window(Content-Length, Content-Disposition etc). I need to download the file instead.
I think the best is to return a FileResult from your controller:
public FileResult DownloadDocument(int Id)
{
var document = new DocumentClient().DownloadDocument(Id);
//do the transformation here
//...
//I don't know what is your file's extension, please replace "application/zip" if
//needed
return File(finalResult, "application/zip", fileName);
}
I'm selfhosting a service. I'm able to HttpGet and HttPut objects. Now I need to return a large File(stream). My question is how to return a large stream.
Below I write the methods I use to get and save a test class Customer.
Possible duplicates:
Selfhosting deal with large files. Alas the answer doesn't help me, it states: make sure that the response content is a StreamContent. Until now I didn't need to write any response content. What should I change to return a StreamContent?
ASP.NET Web API 2 - StreamContent is extremely slow This answer seems to describe the solution to my problem. A HttpRequestMessage object is used to create a HttpResponseMessage object. Then a StreamContent object is assigned to the HttpResponseMessage.Content. But where do I get a HttpRequestMessage, and what should I change in my signatures to be able to return a HttpResponseMessage?
So the duplicates do not help me enough. The answer leave me with a several question.
Until now I'm able to Get and Save an object using a [HttpGet] and [HttpPost]. In my simplified code below I get and save a Customer
To create my server according to the description given y MSDN: Use OWIN to Self-Host ASP.NET
Installed nuget: Microsoft.AspNet.WebApi.OwinSelfHost
Server Side
public class Customer {...}
[RoutePrefix("test")]
public class MyTestController : ApiController
{
[Rout("getcustomer")]
[HttpGet]
public Customer GetCustomer(int customerId)
{
Customer fetchedCustomer = ...;
return fetchedCustomer;
}
[Route("SaveCustomer")
[HttpPost]
public void SaveCustomer(Customer customer)
{
// code to save the customer
}
}
Server side: Main
static void Main(string[] args)
{
var owinserver = WebApp.Start("http://+:8080", (appBuilder) =>
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Formatters.Remove(config.Formatters.XmlFormatter);
appBuilder.UseWebApi(config);
config.EnsureInitialized();
});
Console.WriteLine("Press any key to end");
Console.ReadKey();
}
This is enough to get and set a customer. Apparently this is possible without a HttpRequestMessage.
So my questions:
What is the signature of a function to be able to return a big stream?
Is it enough to assign the StreamContent object as is proposed in the second duplicate?
Apparently the answer is easier than I thought.
In the examples I saw, the return value of a HttpGet was the object that you wanted to return:
[Route("getcustomer")]
[HttpGet]
public Customer GetCustomer(int customerId)
{
Customer fetchedCustomer = ...
return fetchedCustomer;
}
To return a stream, change the return value to a HttpResponseMessage and fill the Content of the HttpRespnseMessage with the Stream you want to return:
[Route("getFileStream")]
[HttpGet]
public Customer GetFileStream(Guid fileId)
{
// get the stream to return:
System.IO.Stream myStream = ...
// get the request from base class WebApi, to create an OK response
HttpResponseMessage responesMessage = this.Request.CreateResponse(HttpStatusCode.OK);
responseMessage.Content = new StreamContent(myStream);
// use extra parameters if non-default buffer size is needed
return responseMessage;
}
Client side:
public class MyOwinFileClient
{
private readonly Owin.Client.WebApiClient webApiClient;
// constructor:
public MyOwinFileClient()
{
this.webApiClient = new Owin.Client.WebApiClient(... url);
}
// the function to get the stream:
public async Task<Stream> GetFileStream(Guid fileId)
{
HttpClient myClient = ...
string requestStreamUri = #"test\GetFileStream?fileId=" + fileId.ToString("N");
HttpResponseMessage responseMessage = await httpClient.GetAsync(requestStreamUri)
.ConfigureAwait(false);
// throw exception if not Ok:
if (!response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new System.Net.Http.HttpRequestException($"{response.StatusCode} [{(int)response.StatusCode}]: {content}");
}
// if here: success: convert response as stream:
Stream stream = await response.Content.ReadAsStreamAsync()
.ConfigureAwait(false);
return stream;
}
}
Usage:
private async void Button1_clicked(object sender, EventArgs e)
{
// download the stream that contains the Bitmap:
Guid bitMapId = ...
MyOwinFileClient myClient = new MyOwinFileClient();
// get the stream:
Stream stream = await myClient.GetFileStream(bitMapId);
// assume the stream to be a bitmap:
Bitmap bmp = new Bitmap(stream);
this.pictureBox1.Image = bmp;
}
For simplicity I left out Dispose
I'm quite new to REST-services in general and I'm playing around with ServiceStack (which is awesome!). I have some services running and now I want to be able to download files (zip) via the service.
My idea is to set a route (/download) to receive files and download them with the client to store them locally.
My current approach looks like this:
[Route("/download")]
public class DownloadRequest : IReturn<HttpResult>
{
}
public class FileDownloadService : Service
{
public object Any(DownloadRequest request)
{
string fileFullPath = #"C:\Users\marcel\Downloads\test.zip";
string mimeType = "application/zip";
FileInfo fi = new FileInfo(fileFullPath);
byte[] reportBytes = File.ReadAllBytes(fi.FullName);
HttpResult result = new HttpResult(reportBytes, mimeType);
result.Headers.Add("Content-Disposition", "attachment;filename=Download.zip;");
return result;
}
}
I'd like to change this implementation to send data as stream. I stumbled upon IStreamWriterAsync, but couldn't really find documentation on usage for this. I'd also like to be able to handle client-side download with the ServiceStack C#-Client.
What would be a good strategy do implement my plan?
Edit: Something like that?
[Route("/download")]
public class DownloadRequest : IReturn<Stream>
{
}
public class FileDownloadService : Service, IHasOptions
{
public IDictionary<string, string> Options { get; private set; }
public Stream Any(DownloadRequest request)
{
string fileFullPath = #"C:\Users\marcel\Downloads\test.zip";
FileInfo fi = new FileInfo(fileFullPath);
Options = new Dictionary<string, string>
{
{"Content-Type","application/zip" },
{"Content-Disposition", "attachment;filename=Download.zip;" }
};
return fi.OpenRead();
}
}
An easy way to download a file is to return the fileInfo in a HttpResult, e.g:
return new HttpResult(new FileInfo(fileFullPath), asAttachment:true);
Or by using the Virtual File System
return new HttpResult(
VirtualFileSources.GetFile(virtualPath), asAttachment:true);
Both of these APIs already write the file bytes as a Stream so there's no need to try manually doing it yourself.
Note: HttpResult is just a server wrapper object not the response body itself so it should never be used in an IReturn<T> interface whose purpose is to tell clients what Response Type the Service returns.
The IReturn<T> should specify what the Response Body is, in this case since it's not a Response DTO it can be either:
IReturn<byte[]> or IReturn<Stream>
Or you can just leave it unspecified as you'll still be able to download it using the ServiceClient's raw data APIs:
With IReturn<Stream> interface:
using (Stream stream = client.Get(new DownloadRequest())) {
...
}
Or you can just easily download the response as a Stream without the IReturn<T> by specifying how you want to access the raw data on the call-site, e.g:
Stream stream = client.Get<Stream>(new DownloadRequest());
byte[] bytes = client.Get<byte[]>("/download");
If you want to also access the Response HTTP Headers you can also request the raw HttpWebResponse to be returned which will let you access the Response HTTP Headers:
using (var webRes = client.Get<HttpWebResponse>(new DownloadRequest()))
using (var stream = webRes.GetResponseStream())
{
var contentDisposition = webRes.Headers[HttpHeaders.ContentDisposition];
}
Alternatively you can also use HTTP Utils to download arbitrary files, e.g:
string info = null;
var bytes = baseUrl.CombineWith("download").GetBytesFromUrl(
responseFilter: res => info = res.Headers[HttpHeaders.ContentDisposition]);
Have a look at this article. Basically, just return a Stream. You can use fi.OpenRead and return that stream.
To combine headers and stream, an option is a custom return type instead, something like this
public class DownloadFileResult : IStreamWriterAsync, IHasOptions
{
private readonly Stream _stream;
public IDictionary<string, string> Options { get; }
public DownloadFileResult(Stream responseStream, string mime, string filename)
{
_stream = responseStream;
Options = new Dictionary<string, string>()
{
{"Content-Disposition", $"attachment; filename=\"{filename}\";"},
{"Content-Type", mime}
};
}
public async Task WriteToAsync(Stream responseStream, CancellationToken token)
{
if (_stream == null) {
return;
}
await _stream.CopyToAsync(responseStream);
responseStream.Flush();
}
}