Introduction
Recently, I needed to make a ServiceStack service which can receive big files, so I wanted to use streaming to accomplish this. Unfortunately, there isn't much information about using stream
s with ServiceStack
, so I decided to share my experience.
We'll create a sample solution containing both Server and Client. We will create a class library containing the service itself and a Utility project. So here is the structure of our solution: let's continue with the implementation of the service. First of all, we'll need to install the ServiceStack
nuget package in the ServiceStackStreaming.Service
project:
PM> Install-Package ServiceStack
This will add the DLLs needed for ServiceSta
ck. Now let's create the DTO:
using ServiceStack.ServiceHost;
namespace ServiceStackStreaming.Service
{
[Route("/upload/{FileName}", "POST")]
public class UploadPackage : IRequiresRequestStream
{
public System.IO.Stream RequestStream { get; set; }
public string FileName { get; set; }
}
}
To enable Streaming support, we need to implement IRequiresRequestStream
which needs a RequestStream
property of type System.IO.Stream
. We'll add a FileName
property and include it in the Route so that we would be able to pass the uploaded file name. The next thing to do is to create the service itself:
using ServiceStack.Common.Web;
using ServiceStackStreaming.Utility;
using System;
using System.IO;
namespace ServiceStackStreaming.Service
{
public class UploadService : ServiceStack.ServiceInterface.Service
{
public object Post(UploadPackage request)
{
if (string.IsNullOrEmpty(request.FileName))
{
var segments = base.Request.PathInfo.Split(new[] { '/' },
StringSplitOptions.RemoveEmptyEntries);
request.FileName = segments[1];
}
string resultFile = Path.Combine(@"C:\Temp", request.FileName);
if (File.Exists(resultFile))
{
File.Delete(resultFile);
}
using (FileStream file = File.Create(resultFile))
{
request.RequestStream.Copy(file);
}
return new HttpResult(System.Net.HttpStatusCode.OK);
}
}
}
Our dummy service will save the incoming file in the "C:\Temp" directory. With the code from line 12 to line 17, we are getting the FileName
property if it's not set. It seems that when using streaming, the additional properties are not processed and they are always null
, so we'll do this little hack to get the properties parsing the request URL. The other trick we use here is the extension method of the System.IO.Stream
class which we have implemented in the ServiceStackStreaming.Utility
project:
using System.IO;
namespace ServiceStackStreaming.Utility
{
public static class StreamExtender
{
public static void Copy(this Stream instance, Stream target)
{
int bytesRead = 0;
int bufSize = copyBuf.Length;
while ((bytesRead = instance.Read(copyBuf, 0, bufSize)) > 0)
{
target.Write(copyBuf, 0, bytesRead);
}
}
private static readonly byte[] copyBuf = new byte[0x1000];
}
}
This simply copies the instance stream to the target stream. Actually we can use Service Stack StreamExtensions WriteTo
method instead.
The last thing we need to do to create a functional service is to add the AppHost
class, we will inherit AppHostHttpListenerBase
as we want to host the service in a window console application.
using ServiceStack.WebHost.Endpoints;
namespace ServiceStackStreaming.Service
{
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Agent", typeof(UploadService).Assembly) { }
public override void Configure(Funq.Container container)
{
}
}
}
We can configure the route here, but I prefer doing this with attribute. Now let's host the service. To do this, we'll need to add the same ServiceStack
nuget to the SertviceStackStreaming.Server
project and add the following code to the Program.cs file:
using ServiceStackStreaming.Service;
using System;
namespace ServiceStackStreaming.Server
{
class Program
{
static void Main(string[] args)
{
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:1999/");
Console.WriteLine("Service listening on port 1999!");
Console.ReadKey();
}
}
}
This will be enough to host the service listening to port 1999. Now let's call the service from the ServiceStackStreaming.Client
(again, we'll have to install the same nuget package here).
using ServiceStackStreaming.Utility;
using System.IO;
using System.Net;
namespace ServiceStackStreaming.Client
{
class Program
{
static void Main(string[] args)
{
string filePath = @"c:\temp\upload.zip";
HttpWebRequest client = (HttpWebRequest)WebRequest.Create
("http://localhost:1999/upload/upload-copy.zip");
client.Method = WebRequestMethods.Http.Post;
client.AllowWriteStreamBuffering = false;
client.SendChunked = true;
client.ContentType = "multipart/form-data;";
client.Timeout = int.MaxValue;
using (FileStream fileStream = File.OpenRead(filePath))
{
fileStream.Copy(client.GetRequestStream());
}
var response = new StreamReader
(client.GetResponse().GetResponseStream()).ReadToEnd();
}
}
}
And that's it. We create WebRequest
, set the needed properties to enable streaming on the client and copy the file stream to the request stream. This will call the service and will upload the "C:\Temp\upload.zip" file as upload-copy.zip file.
You can find the sample code here.