How to upload file (500 MB size and up...) in Restful webservice then save the file in specific location?
If you have links, please share.
I use Fiddler to test the service.
Here are codes that I've been working for.
[WebInvoke(UriTemplate = "Add", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
public bool UploadFile(FileUploader.File userFile, System.IO.Stream fileStream)
{
FileUploader.Logic.StreamObject streamUploader = new FileUploader.Logic.StreamObject();
streamUploader.UploadFile(userFile.FileName, fileStream);
return true;
}
public class StreamObject : IStreamObject
{
public void UploadFile(string filename, Stream fileStream)
{
byte[] buffer = new byte[10000];
int bytesRead, totalbytesRead = 0;
do
{
bytesRead = fileStream.Read(buffer, 0, buffer.Length);
totalbytesRead += bytesRead;
} while (bytesRead > 0);
}
}
The answer stems from what I have done before. The service may look as follows:
[ServiceContract]
public class DocumentService
{
[OperationContract]
[WebTemplate("{name}"]
public Document Create(Stream stream, string name)
{
var id = Guid.NewGuid().ToString("N");
using(FileStream outputStream = File.Create(Path.Combine("c:\\temp\\", id)))
{
stream.CopyTo(outputStream);
}
Document document = new Document();
document.Name = name;
document.Id = id;
// Save document to database
// Set headers
return document;
}
}
where Document may look as follows:
[DataContract]
public class Document
{
[DataMember]
public string Id
{
get;set;
}
[DataMember]
public string Name
{
get;set;
}
/* other fields */
}
To upload a file to the service (assuming it is at http://api.example.com/documents/), you can do the following:
string name = "mydocument.doc";
var request = WebRequest.Create ("http://api.example.com/documents/" + name);
request.ContentType = "application/octet-stream";
request.Method = "POST";
using(var stream = request.GetRequestStream())
{
using(var inputStream = File.OpenRead("c:\\mydocument.doc"))
{
inputStream.CopyTo(stream);
}
}
using(var response = (HttpWebResponse)request.GetResponse())
{
// process the response, if needed
}
There is no need to send a multipart stream over if all you do is sending one stream. It is important that any parameters (such as name) should be part of the UriTemplate.
An example on how to upload a file to a REST service is shown below:
private byte[] UseWebClientForFileUpload(string serviceBaseUrl, String resourceUrl, string filePath)
{
var c = new WebClient();
c.OpenWrite(string.Concat(serviceBaseUrl, resourceUrl), "POST");
c.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
return c.UploadFile(string.Concat(serviceBaseUrl, resourceUrl), filePath);
}
Make sure to set appropriate content type. I think we cannot pass multiple parameters when using Stream as one of the params, so in order to get the filename and the stream just pass everything as a single stream and then use a parser that would seperate your stream. There is something called a multipartParser as shown below:
public class MultipartParser
{
public MultipartParser(Stream stream)
{
this.Parse(stream, Encoding.UTF8);
ParseParameter(stream, Encoding.UTF8);
}
public MultipartParser(Stream stream, Encoding encoding)
{
this.Parse(stream, encoding);
}
private void Parse(Stream stream, Encoding encoding)
{
this.Success = false;
// Read the stream into a byte array
byte[] data = ToByteArray(stream);
// Copy to a string for header parsing
string content = encoding.GetString(data);
// The first line should contain the delimiter
int delimiterEndIndex = content.IndexOf("\r\n");
if (delimiterEndIndex > -1)
{
string delimiter = content.Substring(0, content.IndexOf("\r\n"));
// Look for Content-Type
Regex re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)");
Match contentTypeMatch = re.Match(content);
// Look for filename
re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
Match filenameMatch = re.Match(content);
// Did we find the required values?
if (contentTypeMatch.Success && filenameMatch.Success)
{
// Set properties
this.ContentType = contentTypeMatch.Value.Trim();
this.Filename = filenameMatch.Value.Trim();
// Get the start & end indexes of the file contents
int startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length;
byte[] delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
int endIndex = IndexOf(data, delimiterBytes, startIndex);
int contentLength = endIndex - startIndex;
// Extract the file contents from the byte array
byte[] fileData = new byte[contentLength];
Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength);
this.FileContents = fileData;
this.Success = true;
}
}
}
private void ParseParameter(Stream stream, Encoding encoding)
{
this.Success = false;
// Read the stream into a byte array
byte[] data = ToByteArray(stream);
// Copy to a string for header parsing
string content = encoding.GetString(data);
// The first line should contain the delimiter
int delimiterEndIndex = content.IndexOf("\r\n");
if (delimiterEndIndex > -1)
{
string delimiter = content.Substring(0, content.IndexOf("\r\n"));
string[] splitContents = content.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries);
foreach (string t in splitContents)
{
// Look for Content-Type
Regex contentTypeRegex = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)");
Match contentTypeMatch = contentTypeRegex.Match(t);
// Look for name of parameter
Regex re = new Regex(@"(?<=name\=\"")(.*)");
Match name = re.Match(t);
// Look for filename
re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
Match filenameMatch = re.Match(t);
// Did we find the required values?
if (name.Success || filenameMatch.Success)
{
// Set properties
//this.ContentType = name.Value.Trim();
int startIndex;
if (filenameMatch.Success)
{
this.Filename = filenameMatch.Value.Trim();
}
if(contentTypeMatch.Success)
{
// Get the start & end indexes of the file contents
startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length;
}
else
{
startIndex = name.Index + name.Length + "\r\n\r\n".Length;
}
//byte[] delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
//int endIndex = IndexOf(data, delimiterBytes, startIndex);
//int contentLength = t.Length - startIndex;
string propertyData = t.Substring(startIndex - 1, t.Length - startIndex);
// Extract the file contents from the byte array
//byte[] paramData = new byte[contentLength];
//Buffer.BlockCopy(data, startIndex, paramData, 0, contentLength);
MyContent myContent = new MyContent();
myContent.Data = encoding.GetBytes(propertyData);
myContent.StringData = propertyData;
myContent.PropertyName = name.Value.Trim();
if (MyContents == null)
MyContents = new List<MyContent>();
MyContents.Add(myContent);
this.Success = true;
}
}
}
}
private int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex)
{
int index = 0;
int startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex);
if (startPos != -1)
{
while ((startPos + index) < searchWithin.Length)
{
if (searchWithin[startPos + index] == serachFor[index])
{
index++;
if (index == serachFor.Length)
{
return startPos;
}
}
else
{
startPos = Array.IndexOf<byte>(searchWithin, serachFor[0], startPos + index);
if (startPos == -1)
{
return -1;
}
index = 0;
}
}
}
return -1;
}
private byte[] ToByteArray(Stream stream)
{
byte[] buffer = new byte[32768];
using (MemoryStream ms = new MemoryStream())
{
while (true)
{
int read = stream.Read(buffer, 0, buffer.Length);
if (read <= 0)
return ms.ToArray();
ms.Write(buffer, 0, read);
}
}
}
public List<MyContent> MyContents { get; set; }
public bool Success
{
get;
private set;
}
public string ContentType
{
get;
private set;
}
public string Filename
{
get;
private set;
}
public byte[] FileContents
{
get;
private set;
}
}
public class MyContent
{
public byte[] Data { get; set; }
public string PropertyName { get; set; }
public string StringData { get; set; }
}
I did get the multipartParser from a here