研究IOS上传到WCF图片的小功能,WCF实现服务端的文件上传的例子很多,单独实现IOS发送图片的例子也很多,但是两个结合起来的就很少了。
可以通过base64来上传图片,这个方式比较简单,但是我想要的是通过网络流来传送,这样以后IOS发送任何的文件,服务器不需要修改就能直接来用。想法很简单,但是历程很辛苦。。。
首先研究一下IOS端的图片传输,我用的网络框架是AFNetWorking,附上代码
NSString *filename=@"test.jpg";AFHTTPRequestOperationManager *AFManager=[[AFHTTPRequestOperationManager alloc]initWithBaseURL:[NSURL URLWithString:@FileTranUrl]];AFHTTPRequestOperation *operation=[AFManager POST:path parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formdata){[formdata appendPartWithFileData:imagedata name:name fileName:filename mimeType:@"image/jpeg"];} success:^(AFHTTPRequestOperation *operation, id responSEObject) { if (success) { success(operation,responseObject); }} failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (failure) { failure(operation,error); }}];
imagedata是image转换为NSdata后的值。
这里的代码看起来很多,其实很简单,关键只有一处就是
[formdata appendPartWithFileData:imagedata name:name fileName:filename mimeType:@"image/jpeg"];
AFMultipartFormData
做了什么呢?
查看它的appendPartWithFileData:name:filename:mimeType:
源代码,我们可以看到
- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType{ NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType); NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=/"%@/"; filename=/"%@/"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; [self appendPartWithHeaders:mutableHeaders body:data];}
multableHeaders
包含了name,filename,mimeType再次查看最后一个方法的定义
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body{ NSParameterAssert(body); AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = headers; bodyPart.boundary = self.boundary; bodyPart.bodyContentLength = [body length]; bodyPart.body = body; [self.bodyStream appendHTTPBodyPart:bodyPart];}
[self.bodyStream appendHTTPBodyPart:bodyPart];
看到这里的时候我们已经明白了,以上所有包含的数据全部放到网络流里了。为什么要查看这些呢?因为WCF需要契约定义,我们不知道AFNetwork发送的时候我们应该用什么契约来接受这个方法。
通过上面的分析,我们已经大概知道契约的定义了
void(Stream requestStream)
WCF在接受数据之前还需要进行配置,在
<system.serviceModel><bindings> <webHttpBinding> <binding name="WebConfiguration" maxBufferSize="65536" maxReceivedMessageSize="2000000000" transferMode="Streamed"> </binding> </webHttpBinding> </bindings><services> <!--文件服务--> <service name="WcfServiceForIOS.ServiceForIOSFile" behaviorConfiguration="ServiceBehavior"> <endpoint address="" binding="webHttpBinding" behaviorConfiguration="web" bindingConfiguration="WebConfiguration" contract="WcfServiceForIOS.IServiceForIOSFile" /> </service> </services><behaviors> <endpointBehaviors> <behavior name="web"> <webHttp /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="ServiceBehavior"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> </behavior> </serviceBehaviors></behaviors></system.serviceModel>
尝试实现契约的主要代码
using (targetStream = new FileStream(filePathAndName, FileMode.Create, Fileaccess.Write, FileShare.None)){ const int bufferLen = 4096; Byte[] buffer = new Byte[bufferLen]; int count = 0; while ((count = sourceStream.Read(buffer, 0, bufferLen)) > 0) { targetStream.Write(buffer, 0, count); filesize += count; } targetStream.Close(); sourceStream.Close(); }
等IOS上传图片后,可以在相应的文件夹中找到生成的图片,但是不幸的是我们无法打开,提示图片损坏太大。用NotePad++打开这个文件流,可以看到以下代码
--Boundary+4AA85CFEE4A1D140Content-Disposition: form-data; name="file"; filename="test.jpg"Content-Type: image/jpeg(乱码,目测是图片的数据流)--Boundary+4AA85CFEE4A1D140--
是不是和发送时候的很眼熟,这样和前面的分析就对上了,传输过来的文件流是包含图片的信息和图片的数据。需要分开处理。
处理方法和web发送的form-data是一样的,先编码为string,通过正则表达取出各个属性值
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; } } }
取出各个段后就能用来存文件了
新闻热点
疑难解答