将文件从Html表单(multipart / form-data)上传到WCF REST服务作为流而不流式传输整个表单的输入?

我在将文件从Html上传到我的rest服务(WCF REST)时遇到了问题。 在上传文件时,我想发送标题和说明等信息以及文件的内容。

所以,我创建了一个这样的测试表单:

Title:
Description:
Filename:
File:

服务器端,我想将其翻译为此方法:

 [OperationContract] [WebInvoke( BodyStyle = WebMessageBodyStyle.Bare, Method = "POST", UriTemplate = "/Note/{noteId}/Attachment")] [Description("Add an attachment to a Note.")] void AddAttachmentToNote(string noteId, AttachmentRequestDto attachmentRequestDto); 

将AttachmentRequestDto定义为

 [DataContract] public class AttachmentRequestDto { [DataMember] public string Title { get; set; } [DataMember] public string Description { get; set; } [DataMember] public string Filename { get; set; } [DataMember] public Stream Contents { get; set; } } 

所以,长话短说,我想将标题和描述作为字符串值,同时将文件的内容作为流获取。 这似乎不起作用,因为html表单会将表单的所有内容(以及标题和描述)与文件的内容一起放入流中。 因此,将我的REST方法定义为

 [OperationContract] [WebInvoke( BodyStyle = WebMessageBodyStyle.Bare, Method = "POST", UriTemplate = "/Note/{noteId}/Attachment")] [Description("Add an attachment to a Note.")] void AddAttachmentToNote(string noteId, Stream formContents); 

工作,但后来我需要解析流来获取我的所有数据(与我实际想做的相比,这不是一个很好的方法)。

也许我需要定义两种不同的服务方法,一种只接受文件,另一种接受文件的细节? 但是,这意味着我的业务规则(标题必需+所需的文件内容)应该以不同方式进行validation(因为REST是无状态的)。

值得一提的是:我需要将文件的内容保存在数据库中,而不是保存在文件系统中。

有人对此有一些意见吗? 我有点卡在上面……

谢谢!

请查找一些代码,这些代码可以帮助您在一次调用REST服务时传递文件及其详细信息:

首先,您需要一个名为MultipartParser的东西,如下所示:

 using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace SampleService { public class MultipartParser { private byte[] requestData; 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); requestData = data; // 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; if (requestData.Length == 0) { data = ToByteArray(stream); } else { data = requestData; } // 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(); 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(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 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; } } } 

现在定义您的REST方法,如下所示:

 [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest)] AttachmentRequestDto AddAttachmentToNote(Stream stream); 

现在实现上面的方法如图所示:

 public AttachmentRequestDto AddAttachmentToNote(Stream stream) { MultipartParser parser = new MultipartParser(stream); if(parser != null && parser.Success) { foreach (var content in parser.MyContents) { // Observe your string here which is a serialized version of your file or the object being passed. Based on the string do the necessary action. string str = Encoding.UTF8.GetString(content.Data); } } return new AttachmentRequestDto(); } 

My AttachmentRequestDto如下所示:

 [DataContract] public class AttachmentRequestDto { [DataMember] public string Title { get; set; } [DataMember] public string Description { get; set; } [DataMember] public string Filename { get; set; } } 

现在从客户端我执行POST,如下所示:

 Image image = Image.FromFile("C:\\Users\\Guest\\Desktop\\sample.png"); MemoryStream ms = new MemoryStream(); image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); byte[] imageArray = ms.ToArray(); ms.Close(); AttachmentRequestDto objAttachmentRequestDto = new AttachmentRequestDto(); objAttachmentRequestDto.Title = "Sample"; objAttachmentRequestDto.Description = "Sample book"; objAttachmentRequestDto.FileName = "SampleBook.png"; var serializer = new DataContractSerializer(typeof(AttachmentRequestDto)); var ms = new MemoryStream(); serializer.WriteObject(ms, objAttachmentRequestDto); ms.Position = 0; var reader = new StreamReader(ms); string requestBody = reader.ReadToEnd(); var client = new RestClient(); client.BaseUrl = "http://localhost/SampleService/Service1.svc"; var request = new RestRequest(method) { DateFormat = DataFormat.Xml.ToString(), Resource = resourceUrl }; if(requestBody !=null) request.AddParameter("objAttachmentRequestDto", requestBody); request.AddFile("stream", image, "Array.png"); var response = client.Execute(request); 

我确实使用第三方dll作为名为RESTSharp的上述代码。

一旦在服务器上,MultipartParser识别您的请求边界以分割必要的内容,然后您可以决定如何处理每个拆分内容(保存到文件,数据库等)。

只需确保您具有上述的approriate配置条目以及为we​​bHttpBinding设置的dataContractSerializer属性和readerQuotas。

注意:如果需要,可以更多地重新考虑MultipartParser。