簡體   English   中英

將文件從Html表單(multipart / form-data)上傳到WCF REST服務作為流而不流式傳輸整個表單的輸入?

[英]Upload file from Html form (multipart/form-data) to WCF REST service as a stream without streaming the whole form's inputs?

我在將文件從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是無狀態的)。

值得一提的是:我需要將文件的內容保存在數據庫中,而不是保存在文件系統中。

有沒有人對此有一些意見? 我有點卡在上面......

謝謝!

請查找一些代碼,這些代碼可以幫助您在一次調用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配置條目以及為we​​bHttpBinding設置的dataContractSerializer屬性和readerQuotas。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM