我在将文件从Html上传到我的休息服务(WCF REST)时遇到了问题。在上传文件时,我想发送标题和说明等信息以及文件的内容。
所以,我创建了一个这样的测试表:
<form id="testForm" action="http://localhost.:1576/NotepadService.svc/Note/91f6413c-4d72-42ca-a0f3-38df15759fc9/Attachment" method="POST" enctype="multipart/form-data">
<table>
<tr><td>Title:</td><td><input type="text" name="Title"></td></tr>
<tr><td>Description:</td><td><input type="text" name="Description"></td></tr>
<tr><td>Filename:</td><td><input type="text" name="Filename"></td></tr>
<tr><td>File:</td><td><input type="file" name="Contents"></td></tr>
<tr><td/><td><input type="submit" value="Send"></td></tr>
</table>
</form>
服务器端,我想将其翻译为此方法:
[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);
有效,但后来我需要解析流来获取我的所有数据(与我实际想做的相比,这不是一个好方法)。
也许我需要定义两种不同的服务方法,一种只接受文件,另一种接受文件的详细信息?但是,这意味着我的业务规则(标题必需+所需的文件内容)应该以不同方式进行验证(因为REST是无状态的)。
可能值得一提的东西:我需要将文件的内容保存在数据库中,而不是保存在文件系统中。
有人对此有一些意见吗?我有点坚持......
谢谢!
答案 0 :(得分:6)
请找一些代码,这些代码可以帮助您在一次调用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<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; }
}
}
现在定义您的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配置条目以及为webHttpBinding设置的dataContractSerializer属性和readerQuotas。
注意:如果需要,可以更多地重新考虑MultipartParser。