Download files with ServiceStack Rest-API - c#

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();
}
}

Related

ASP.Net MVC return file as attachment from external API

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);
}

Save Stream to file: how important is BufferSize

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,
};
}

Owin Selfhosting return data in streamed mode

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

ServiceStack: How to unit test a service that serves files

I have a web service that service an Excel file
public class ReportService : Service
{
public IReportRepository Repository {get; set;}
public object Get(GetInvoiceReport request)
{
var invoices = Repository.GetInvoices();
ExcelReport report = new ExcelReport();
byte[] bytes = report.Generate(invoices);
return new FileResult(bytes);
}
}
and I setup the object that is retured from the service as
public class FileResult : IHasOptions, IStreamWriter, IDisposable
{
private readonly Stream _responseStream;
public IDictionary<string, string> Options { get; private set; }
public BinaryFileResult(byte[] data)
{
_responseStream = new MemoryStream(data);
Options = new Dictionary<string, string>
{
{"Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{"Content-Disposition", "attachment; filename=\"InvoiceFile.xlsx\";"}
};
}
public void WriteTo(Stream responseStream)
{
if (_responseStream == null)
return;
using (_responseStream)
{
_responseStream.WriteTo(responseStream);
responseStream.Flush();
}
}
public void Dispose()
{
_responseStream.Close();
_responseStream.Dispose();
}
}
Now, the webservice works fine when tested through a browser; but it gives an error message when tested from a unit test. Below is the error message:
System.Runtime.Serialization.SerializationException : Type definitions
should start with a '{', expecting serialized type 'FileResult', got
string starting with:
PK\u0003\u0004\u0014\u0000\u0008\u0000\u0008\u0000�\u000b5K���%\u0001\u0000\u0000�\u0003\u0000\u0000\u0013\u0000\u0000\u0000[Content_Types].xml��
at
ServiceStack.Text.Common.DeserializeTypeRefJson.StringToType(TypeConfig
typeConfig, StringSegment strType, EmptyCtorDelegate ctorFn,
Dictionary2 typeAccessorMap) at
ServiceStack.Text.Common.DeserializeType1.<>c__DisplayClass2_0.b__1(StringSegment value) at ServiceStack.Text.Json.JsonReader1.Parse(StringSegment
value) at ServiceStack.Text.Json.JsonReader1.Parse(String value)
at ServiceStack.Text.JsonSerializer.DeserializeFromString[T](String
value) at
ServiceStack.Text.JsonSerializer.DeserializeFromStream[T](Stream
stream) at
ServiceStack.ServiceClientBase.GetResponse[TResponse](WebResponse
webResponse) at
ServiceStack.ServiceClientBase.Send[TResponse](String httpMethod,
String relativeOrAbsoluteUrl, Object request)
Below is the unit test I used to test the webservice:
[Test]
public void TestInvoiceReport()
{
var client = new JsonServiceClient("http://localhost/report/");
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "[User Name]",
Password = "[Password]"
});
var requestDTO = new GetInvoiceReport();
var ret = client.Get<FileResult>(requestDTO);
Assert.IsTrue(ret != null);
}
Edit:
I am including the definition for my request DTO class:
[Route("/invoices", "GET")]
public class GetInvoiceReport: IReturn<FileResult>
{
}
Any help is appreciated.
Note: if you're making a HTTP Request instead of calling the Service in code, it's an Integration Test instead of a Unit Test.
You haven't provided your GetInvoiceReport Request DTO definition, but if you're returning anything that's not a serialized DTO it should be specified it its IReturn<T> interface, e.g:
public class GetInvoiceReport : IReturn<byte[]> { ... }
Then you'll be able to download the raw bytes with:
byte[] response = client.Get(new GetInvoiceReport());
You can use the Service Clients Request Filters for inspecting the HTTP Response Headers.
I'd also recommend checking out ServiceStack's .NET Service Clients docs which contains extensive info for downloading raw Responses.

Posting Audio (byte array) to MVC.NET Web Api

The Problem
I am trying to send audio recorded by my android device to a MVC.NET Web Api. I confirmed the connection by passing simple string parameters. However, when I try to pass the byte array generated from the audio, I get a 500 from the server every time. I have tried multiple configurations, but here's what I currently have:
MVC Web API
public class PostParms
{
public string AudioSource { get; set; }
public string ExtraInfo { get; set; }
}
public class MediaController : ApiController
{
[HttpPost]
public string Post([FromBody]PostParms parms)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(parms.AudioSource);
return "success";
}
}
Android Code
public class WebServiceTask extends AsyncTask<String, Void, Long>
{
#Override
protected Long doInBackground(String... parms)
{
long totalsize = 0;
String filepath = parms[0];
byte[] fileByte = convertAudioFileToByte(new File(filepath));
//Tried base64 below with Convert.FromBase64 on the C# side
//String bytesstring = Base64.encodeToString(fileByte, Base64.DEFAULT);
String bytesstring = "";
try
{
String t = new String(fileByte,"UTF-8");
bytesstring = t;
} catch (UnsupportedEncodingException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
URL url;
HttpURLConnection urlConnection = null;
try {
url = new URL("http://my.webservice.com:8185/api/media");
//setup for connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setUseCaches(false);
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type","application/json");
urlConnection.setRequestProperty("Accept","application/json");
urlConnection.connect();
JSONObject json = new JSONObject();
json.put("AudioSource", bytesstring);
json.put("ExtraInfo", "none");
DataOutputStream output = new DataOutputStream(urlConnection.getOutputStream());
output.writeBytes(URLEncoder.encode(json.toString(),"UTF-8"));
output.flush();
output.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
urlConnection.disconnect();
}
return totalsize;
}
protected void onPreExecute(){
}
protected void onPostExecute(){
}
private byte[] convertAudioFileToByte(File file) {
byte[] bFile = new byte[(int) file.length()];
try {
// convert file into array of bytes
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(bFile);
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return bFile;
}
}
I can get a .NET application to work with the same API sending an audio stream, but not Android. Like I said, I've tried a number of configurations found on the internet with encoding and the output stream, but continue to get 500. I need help, as I am at a loss.
Thanks in advance
I used a ByteArrayEntity instead of a StringEntity.
// ByteArrayEntity
HttpPost request = new HttpPost(getString(R.string.server_url) + "SendMessage");
ByteArrayEntity be = new ByteArrayEntity(msg);
be.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded"));
request.setEntity(be);
HttpResponse response = client.execute(request);
On the server side, I used this web API code to get the byte array:
[HttpPost]
public async Task<string> SendMessage()
{
byte[] arrBytes = await Request.Content.ReadAsByteArrayAsync();
return "";
}
I just posted a question on Stack Overflow at Uploading/Downloading Byte Arrays with AngularJS and ASP.NET Web API that is relevant (in part) to your question here. You might want to read that post to set the context for my response. It is about sending and receiving byte[]’s via Web API. I have one issue with the approach, but it has to do with the JavaScript client incorrectly handling the server response.
I would make the following changes to your Server-Side code as follows:
public class PostParms
{
public string ExtraInfo { get; set; }
}
public class MediaController : ApiController
{
[HttpPost]// byte[] is sent in body and parms sent in url
public string Post([FromBody] byte[] audioSource, PostParms parms)
{
byte[] bytes = audioSource;
return "success";
}
}
Read the post at http://byterot.blogspot.com/2012/04/aspnet-web-api-series-part-5.html
Use/add Byte Rot’s asynchronous version of the Media Formatter for byte[]’s.
I had to tweak the formatter as shown below for a newer version of ASP.NET.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace YOUR_NAMESPACE
{
public class BinaryMediaTypeFormatter : MediaTypeFormatter
{
/****************** Asynchronous Version ************************/
private static Type _supportedType = typeof(byte[]);
private bool _isAsync = true;
public BinaryMediaTypeFormatter() : this(false){
}
public BinaryMediaTypeFormatter(bool isAsync) {
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
IsAsync = isAsync;
}
public bool IsAsync {
get { return _isAsync; }
set { _isAsync = value; }
}
public override bool CanReadType(Type type){
return type == _supportedType;
}
public override bool CanWriteType(Type type){
return type == _supportedType;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream,
HttpContent contentHeaders, IFormatterLogger formatterLogger){// Changed to HttpContent
Task<object> readTask = GetReadTask(stream);
if (_isAsync){
readTask.Start();
}
else{
readTask.RunSynchronously();
}
return readTask;
}
private Task<object> GetReadTask(Stream stream){
return new Task<object>(() =>{
var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
});
}
private Task GetWriteTask(Stream stream, byte[] data){
return new Task(() => {
var ms = new MemoryStream(data);
ms.CopyTo(stream);
});
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream,
HttpContent contentHeaders, TransportContext transportContext){ // Changed to HttpContent
if (value == null)
value = new byte[0];
Task writeTask = GetWriteTask(stream, (byte[])value);
if (_isAsync){
writeTask.Start();
}
else{
writeTask.RunSynchronously();
}
return writeTask;
}
}
}
I also had to add the following line to the config file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// This line added to activate your binary formatter.
config.Formatters.Add(new BinaryMediaTypeFormatter());
/*********************************** Client-Code *************************************/
You will need to use 'Content-Type': 'application/octet-stream' on the client.
You can send the byte[] as binary, but it has been too long since I have coded in C# on the client.
I think that the line: output.writeBytes(URLEncoder.encode(json.toString(),"UTF-8")); will have to be changed as a minimum.
I will have a look and see if I can find some C# client code snippets that might be relevant.
Edit You might have a look at the C# client code of the following post. It may help you to tweak your code to work with the approach that I suggested.
How to Get byte array properly from an Web Api Method in C#?

Categories

Resources