ServiceStack Stream Compression - c#

I am returning a stream of data from a ServiceStack service as follows. Note that I need to do it this way instead of the ways outlined here because I need to perform some cleanup after the data has been written to the output stream.
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
fs.WriteTo(Response.OutputStream);
}
Response.EndRequest();
...cleanup code...
Compression is handled in the other services that return simple DTOs by using a ServiceRunner similar to this answer. However the stream response above never hits that code as the response object in OnAfterExecute is always null. I am able to manually compress the result inside of the service method as follows, but it requires a lot of setup to determine if and what compression is needed and manually setting up the correct HTTP headers (omitted below).
var outStream = new MemoryStream();
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress))
{
fs.CopyTo(tinyStream);
outStream.WriteTo(Response.OutputStream);
}
Response.EndRequest();
...cleanup code...
Is there a way in ServiceStack to handle this compression for me similar to the way it works with the ServiceRunner?

I'm not exactly sure what you like about the way ServiceStack handles compression within ServiceRunner. Is it because it is global across ServiceStack APIs?
For your example, I think something like below works and meets your need to perform some cleanup after the data has been written to the output stream ...
public class FStreamService : Service
{
public object Get(FStream request)
{
var filePath = #"c:\test.xml";
var compressFileResult = new CompressedFileResult(filePath); //CompressedResult in ServiceStack.Common.Web.CompressedFileResult
compressFileResult.WriteTo(Response.OutputStream);
Response.EndRequest();
// ...cleanup code...
}
}
Based on comments update of above to add compression using some ServiceStack extensions
public object Get(FStream request)
{
var filePath = #"c:\test.xml";
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
var compressedBtyes = fs.ToUtf8String().Compress(this.RequestContext.CompressionType);
new CompressedResult(compressedBtyes).WriteTo(Response.OutputStream);
}
Response.EndRequest();
// ...cleanup code...
}
If you want it within a ServiceRunner something like this should work...
public override object OnAfterExecute(IRequestContext requestContext, object response)
{
var resp = requestContext.Get<IHttpResponse>();
response = requestContext.ToOptimizedResult(requestContext.Get<IHttpResponse>().OutputStream);
return base.OnAfterExecute(requestContext, response);
}

Related

Convert List<string> to a stream

The problem I have is that I have a CSV file full of records, that currently is being mapped to a strongly typed collection via the open source CsvHelper.CsvReader.GetRecords<T> method. It gets passed a GZIP stream which is built on a FileStream so is reading the stream from disk.
My suspicion is that the CsvHelper class when used with a FileStream is not very efficient as this load takes a long time. I want to try and load the raw file efficiently first just into memory, and then do the strong type mapping afterwards.
Unfortunately, the mapping class CsvHelper.CsvReader.GetRecords<T> accepts only a stream. I have managed to load the raw data into a List<string> very fast, however I now cannot figure out how to "streamify" this to pass to the mapper. Is this something I can do or is there another solution?
My code so far is
var fileStream = ...
var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
var entries = new List<string>();
using (var unzip = new StreamReader(gzipStream))
while(!unzip.EndOfStream)
entries.Add(unzip.ReadLine());
Parse(??);
public IReadOnlyCollection<TRow> Parse(Stream stream)
{
Func<Stream> streamFactory = () => stream;
var results = ParseCsvWithConfig <TRow>(streamFactory, _configuration).AsReadOnly();
}
public static IEnumerable<T> ParseCsvWithConfig<T>(Func<Stream> streamFactory, CsvConfiguration configuration)
{
using (var stream = streamFactory())
{
var streamReader = new StreamReader(stream);
using (var csvReader = new CsvReader(streamReader, configuration ?? new CsvConfiguration()))
{
return csvReader.GetRecords<T>().ToList();
}
}
}
Skip the list altogether:
var fileStream = ...
var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
var memoryStream = new MemoryStream();
gzipStream.CopyTo(memoryStream);
// call Parse on memorystream
Feel free to add using blocks where appropriate in your code.

Remove temporary file after returning in raw format in WCF

I have a WCF service which works in raw format, using streams:
[ServiceContract]
public interface IEncryptingService
{
[WebInvoke(UriTemplate = "/")]
[OperationContract]
Stream SignDocument(Stream requestStream);
}
public class EncryptingService : IEncryptingService
{
public Stream SignDocument(Stream requestStream)
{
string originalFileName = Path.GetTempFileName();
string signedFileName = Path.GetTempFileName();
using (var originalFileStream = File.Open(originalFileName, FileMode.Create, FileAccess.Write))
{
requestStream.CopyTo(originalFileStream);
}
XmlDocumentSigner.SignFile(originalFileName, signedFileName);
return File.Open(signedFileName, FileMode.Open, FileAccess.Read);
}
}
Now, how can I remove this file after WCF ends returning the file?
I have tried to use finally block, but it gets called right after return, and throws exception, since the file is still used by a process.
Of course, these is a workaround like a background worker waiting for a file to be available for deletion, but, in my opinion, it is not like how web-services should be implemented.
I haven't tried it but you could open the file and write that stream to another stream. Something like:
MemoryStream ms = new MemoryStream();
using (FileStream fs = File.OpenRead(signedFileName))
{
//Read from fs and write to ms
}
Then all you will need to do is call delete on the file and return ms:
File.Delete(signedFileName);
return ms;
The solution came to mind inexcusably quickly, and it is absolutely logical: I can simply read file contents to memory and remove the file.
public Stream SignDocument(Stream requestStream)
{
string originalFileName = Path.GetTempFileName();
string signedFileName = Path.GetTempFileName();
using (var originalFileStream = File.Open(originalFileName, FileMode.Create, FileAccess.Write))
{
requestStream.CopyTo(originalFileStream);
}
XmlDocumentSigner.SignFile(originalFileName, signedFileName);
byte[] signedFileBytes = File.ReadAllBytes(signedFileName);
File.Delete(signedFileName);
return new MemoryStream(signedFileBytes);
}
Note that using statement makes this code fail as well:
using (var ms = new MemoryStream(signedFileBytes))
{
return ms;
}

Download ZipArchive from c# web api method returns "net::ERR_CONNECTION_RESET" in chrome

I want to call a web api method and have it allow the user to download a zip file that I create in memory. I also want to create the entries in memory as well.
I'm having trouble getting the server to correctly output the download.
Here is my web api method:
[HttpGet]
[Route("api/downloadstaffdata")]
public HttpResponseMessage DownloadStaffData()
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
using (var stream = new MemoryStream())
{
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
{
//future for loop to create entries in memory from staff list
var entry = archive.CreateEntry("bob.txt");
using (var writer = new StreamWriter(entry.Open()))
{
writer.WriteLine("Info for: Bob");
}
//future add staff images as well
}
stream.Seek(0, SeekOrigin.Begin);
response.Content = new StreamContent(stream);
}
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "staff_1234_1.zip"
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip");
return response;
}
Here is my calling js code:
window.open('api/downloadstaffdata');
Here is the response from Chrome:
net::ERR_CONNECTION_RESET
I don't know what I'm doing wrong. I've already searched SO and read the articles about creating the zip file, but I can't get passed the connection reset error when trying to return the zip archive to the client.
Any ideas?
You have your memory stream inside a using block. As such, your memory stream are being disposed before your controller has the chance to write it out (hence the ERR_CONNECTION_RESET).
A MemoryStream does not need to be disposed explicitly (its various derived type may need to be, but not the MemoryStream itself). Garbage Collector can clean it up automatically.

Deserializing an array using SOAP

I'm having a problem getting a SOAP service to deserialize an array of objects; its only reading in the first member of the array and nothing else. Here is my code:
public void StoreCredentials(Credentials credentials)
{
Credentials[] credsArray;
var soap = new SoapFormatter();
var stream = new FileStream(_path, FileMode.OpenOrCreate);
try
{
credsArray = (Credentials[])soap.Deserialize(stream);
var credsList = credsArray.ToList();
credsList.Add(credentials);
credsArray = credsList.ToArray();
}
catch (SerializationException)
{
credsArray = new[] {credentials};
}
soap.Serialize(stream, credsArray);
stream.Close();
}
I wrote a simple unit test which adds two Credentials objects to the file, the output looks correct, both sets of credentials are present, but when I run the test to add a third set to the file, the soap.Deserialize(stream) line returns an array with only one entry, even though the file its reading from contains two entries. Am I doing something wrong here? Is there a better / easier way to do this? Please help!
I figured it out. The problem is with the FileMode.OpenOrCreate attribute of the FileStream. This will open the file but append it with a new collection instead of altering the original collection. I needed to overwrite the file by using FileMode.Create instead, so here is the working code (I also changed the collection to a Hashtable, which works better):
public void StoreCredentials(Credentials credentials)
{
credentials.Encrypt(_myUser.Encryption);
Hashtable credsTable;
var soap = new SoapFormatter();
FileStream stream;
try
{
stream = new FileStream(_path, FileMode.Open, FileAccess.ReadWrite);
credsTable = (Hashtable) soap.Deserialize(stream);
stream.Close();
stream = new FileStream(_path, FileMode.Create, FileAccess.ReadWrite);
if (credsTable.ContainsKey(credentials.Id))
credsTable[credentials.Id] = credentials;
else
credsTable.Add(credentials.Id, credentials);
}
catch (FileNotFoundException e)
{
stream = new FileStream(_path, FileMode.Create, FileAccess.ReadWrite);
credsTable = new Hashtable {{credentials.Id, credentials}};
}
soap.Serialize(stream, credsTable);
stream.Close();
}

MemoryStream to virtual file

I have the following:
using(var memoryStream = new MemoryStream())
{
gc.CreatePackage(memoryStream);
}
MemoryStream spits out an excel fil. From the memoryStream, how do I go about actually showing the file that is produced. Note that I do not want to actually save the file to disk but merely display it.
So far I have the following but doesn't seem to work:
using (var memoryStream = new MemoryStream())
{
gc.CreatePackage(memoryStream);
using (var fileStream = File.OpenWrite("Test.xlsx"))
{
memoryStream.WriteTo(fileStream);
}
}
But not sure if I am on the right direction. I get an error saying:
System.Web.Mvc.Controller.File(byte[], string)' is a 'method', which
is not valid in the given context
I am not sure if I am going about this in the right direction.
Making the assumption that you are using ASP.NET MVC, you probably want the File response helper:
using (var memoryStream = new MemoryStream())
{
gc.CreatePackage(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
return File(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
If you want to have the file source actually appear in the browser, you can also lie about the MIME type and use text/plain instead. The browser will most likely render this as plain text.
You can also add a third parameter to File in order to specify the filename the download should appear to the end-user to be.
Since the error you post seems to indicate that you're trying to return a generated file from an MVC controller, I think this may be what you're looking for.
public ActionResult MyAction()
{
using (var memoryStream = new MemoryStream())
{
gc.CreatePackage(memoryStream);
//Make sure the position of the stream is at 0
memoryStream.Position = 0;
//Return the contents of the stream with the appropriate MIME type
return File(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
}
You could use a MemoryMappedFile to create a virtual file.
example code:
Write:
using (var mmf = MemoryMappedFile.CreateNew("MappedFileName", size, MemoryMappedFileAccess.ReadWriteExecute))
{
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
{
accessor.Write(Write your data to mapped file);
}
}
Read:
using (var mmf = MemoryMappedFile.OpenExisting("MappedFileName"))
{
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
{
accessor.Read......
}
}

Categories

Resources