在爲RESTful的WCF使用了newtonsoftJson轉化器之後,現在需要一個新功能,爲REST WCF增加一個能上傳文件的藉口,如果沒有自定義newtonsoftJson,這個東西只需要寥寥幾筆,就直接實現了文件上傳,但現在自定義了之後,就不行了,因爲自定義的NewtonsoftJsonDispatchFormatter類的DeserializeRequest託管了一切,讓原有的WCF文件上傳功能失去了作用,於是決定自己改造這個類,關於http文件上傳,w3c是有rfc1867規範的,根據這個規範,就有了如下的修改。(此修改只是實現了單文件上傳,多文件上傳還要再次簡單修改一下)
NewtonsoftJsonDispatchFormatter->DeserializeRequest方法
public void DeserializeRequest(Message message, object[] parameters)
{
//如果是上傳的content-type,則不作json處理
var headers = ((HttpRequestMessageProperty)(message.Properties[HttpRequestMessageProperty.Name])).Headers;
string contenttype = headers["Content-type"];
string contentLength = headers["Content-Length"];
if (contenttype.StartsWith("multipart/form-data"))
{
//獲得附件分割邊界字符串
string boundary = contenttype.Substring(contenttype.IndexOf("boundary=") + "boundary=".Length);
int len = int.Parse(contentLength);
//if(!(bool)(message.Properties["UriMatched"]))
// throw new InvalidOperationException("URL not match.");
//UriTemplateMatch utr = message.Properties["UriTemplateMatchResults"] as UriTemplateMatch;
//var d = OperationContext.Current;
//var m=utr.Template.Match(utr.BaseUri,message.Headers.To);
//獲得方法的參數
var paramts=operation.SyncMethod.GetParameters();
int streamtypeIndx = -1;
int bodyheaderlen = 0;
//找到Stream類型的參數
for (streamtypeIndx = 0; streamtypeIndx < paramts.Length && streamtypeIndx < parameters.Length; streamtypeIndx++)
{
if(paramts[streamtypeIndx].ParameterType==typeof(Stream))
{
//var fl=HttpContext.Current.Request.Files[0];
//var stream = fl.InputStream;
var stream = message.GetBody<Stream>();
//定位到第一個0D0A0D0A)
//sb= new StringBuilder(512);
int datimes = 0;//回車換行次數
int c = 0;
while (datimes != 4)
{
c=stream.ReadByte();
if (c == -1)
break;
if (c == 0x0d && (datimes == 0 || datimes == 2))
{
datimes++;
}
else if (c == 0x0a && (datimes == 1 || datimes == 3))
{
datimes++;
}
else
datimes = 0;
//sb.Append((char)c);
bodyheaderlen++;
}
if (c == -1)
continue;
//計算實際附件大小
int fileLength = len - bodyheaderlen - boundary.Length - 6;
int remain = fileLength;
MemoryStream filestream = new MemoryStream(fileLength);
byte[] buffer=new byte[8192];
int readed = 0;
while (remain>0)
{
readed = stream.Read(buffer, 0, remain > 8192 ? 8192 : remain);
remain -= readed;
filestream.Write(buffer, 0, readed);
}
stream.Close();
filestream.Seek(0, SeekOrigin.Begin);
//MemoryStream stream = new MemoryStream(message.GetReaderAtBodyContents().ReadElementContentAsBinHex());
parameters[streamtypeIndx] = filestream;
}
else
{
//string va;
//if(headers.t)
parameters[streamtypeIndx] = headers[paramts[streamtypeIndx].Name];
}
}
//if(streamtypeIndx>=parameters.Length)
// throw new InvalidOperationException("No System.IO.Stream type parameter in API Method.");
return;
}
object bodyFormatProperty;
if (!message.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out bodyFormatProperty) ||
(bodyFormatProperty as WebBodyFormatMessageProperty).Format != WebContentFormat.Raw)
{
throw new InvalidOperationException("Incoming messages must have a body format of Raw. Is a ContentTypeMapper set on the WebHttpBinding?");
}
XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
bodyReader.ReadStartElement("Binary");
byte[] rawBody = bodyReader.ReadContentAsBase64();
MemoryStream ms = new MemoryStream(rawBody);
StreamReader sr = new StreamReader(ms);
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer() {
DateFormatString = "yyyy-MM-dd HH:mm:ss"
};
if (parameters.Length == 1)
{
// single parameter, assuming bare
parameters[0] = serializer.Deserialize(sr, operation.Messages[0].Body.Parts[0].Type);
}
else
{
// multiple parameter, needs to be wrapped
Newtonsoft.Json.JsonReader reader = new Newtonsoft.Json.JsonTextReader(sr);
reader.Read();
if (reader.TokenType != Newtonsoft.Json.JsonToken.StartObject)
{
throw new InvalidOperationException("Input needs to be wrapped in an object");
}
reader.Read();
while (reader.TokenType == Newtonsoft.Json.JsonToken.PropertyName)
{
string parameterName = reader.Value as string;
reader.Read();
if (this.parameterNames.ContainsKey(parameterName))
{
int parameterIndex = this.parameterNames[parameterName];
parameters[parameterIndex] = serializer.Deserialize(reader, this.operation.Messages[0].Body.Parts[parameterIndex].Type);
}
else
{
reader.Skip();
}
reader.Read();
}
reader.Close();
}
sr.Close();
ms.Close();
}
之後,在自己的Web方法中,只要包含一個Stream參數,參數值就是文件的二進制流
此代碼在Fiddler2上測試通過,再次聲明,現在只支持一個文件上傳
上傳文件的大小限制,可以在配置文件中進行修改
<webHttpBinding>
<binding name="test1" maxReceivedMessageSize="6291456" maxBufferSize="6291456" contentTypeMapper="xxx.xxx.xxx,xxx">
這裏的maxReceivedMessageSize大體上等於文件的大小