- 所支持的属性:MessageHeaderAttribute、MessageBodyMemberAttribute
- 用处:添加自定义头(custom headers)、控制消息是否被包装、控制签名与加密。
/// <summary> /// 上传文件 /// </summary> [MessageContract(IsWrapped = true, ProtectionLevel = ProtectionLevel.Sign)] public class UpFile { /// <summary> /// 文件大小 /// </summary> [MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)] public long FileSize { get; set; } /// <summary> /// 文件名称 /// </summary> [MessageHeader] public string FileName { get; set; } /// <summary> /// 目录名称 /// </summary> [MessageHeader] public string DictoryName { get; set; } /// <summary> /// 是否拷贝 /// </summary> [MessageHeader] public bool IsCopy { get; set; } /// <summary> /// 文件流 /// </summary> [MessageBodyMember] public Stream FileStream { get; set; } /// <summary> /// 客户代码 /// </summary> [MessageHeader] public string CustomerCode { get; set; } }1、 MessageContract--将一个类型转换为SOAP消息,类型可以包含消息头和消息体的元素, 该特性含有以下几个属性:
- IsWrapped:是否为定义的主体成员(一个或者多个)添加一个额外的根节点。
- WrapperName:根节点的名称。
- WrapperNamespace:根节点的命名空间。
- ProtectionLevel:表示保护级别,WCF中通过System.Net.Security.ProtectionLevel枚举定义消息的保护级别。一般有3种可选的保护级别:None、Sign和EncryptAndSign。
- Actor:为一个URI值,表示处理该报头的目标节点。
- MustUnderstand:bool类型,表明Actor定义的节点是否必须理解并处理该节点。
- Name:名称。
- Namespace:命名空间。
- ProtectionLevel:表示保护级别。
- Relay:表明该报头是否需要传递到下一个SOAP节点。
- Order:Order属性用来控制成员在SOAP主体部分中出现的位置,默认按字母顺序排列。
- Name:名称。
- Namespace:命名空间。
- ProtectionLevel:表示保护级别。
介绍完消息契约的相关特性,接下来我们将详细介绍如何应用。我们的服务端采用三层结构,我们的定义的接口放在IDAL中,为了方便,我们相对应的MessageContract创建在IDAL接口类中。我们需要定义下载模型、下载结果以及上传模型/上传结果。对应的代码如下:[备注:我们这边不需要对应的特性,以下代码相对应减去]
/// <summary> /// 下载模型 /// </summary> [MessageContract] public class DownFile { /// <summary> /// 下载文件名称 /// </summary> [MessageHeader] public string FileName { get; set; } /// <summary> /// 客户代码 /// </summary> [MessageHeader] public string CustomerCode { get; set; } } /// <summary> /// 上传结果 /// </summary> [MessageContract] public class UpFileResult { /// <summary> /// 是否上传成功 /// </summary> [MessageHeader] public bool IsSuccess { get; set; } /// <summary> /// 返回消息 /// </summary> [MessageHeader] public string Message { get; set; } /// <summary> /// 拷贝文件名称 /// </summary> [MessageHeader] public string CopyFileName { get; set; } } /// <summary> /// 上传文件 /// </summary> [MessageContract] public class UpFile { /// <summary> /// 文件大小 /// </summary> [MessageHeader] public long FileSize { get; set; } /// <summary> /// 文件名称 /// </summary> [MessageHeader] public string FileName { get; set; } /// <summary> /// 目录名称 /// </summary> [MessageHeader] public string DictoryName { get; set; } /// <summary> /// 是否拷贝 /// </summary> [MessageHeader] public bool IsCopy { get; set; } /// <summary> /// 文件流 /// </summary> [MessageBodyMember] public Stream FileStream { get; set; } /// <summary> /// 客户代码 /// </summary> [MessageHeader] public string CustomerCode { get; set; } } /// <summary> /// 下载文件结果 /// </summary> [MessageContract] public class DownFileResult { /// <summary> /// 文件大小 /// </summary> [MessageHeader] public long FileSize { get; set; } /// <summary> /// 下载是否成功 /// </summary> [MessageHeader] public bool IsSuccess { get; set; } /// <summary> /// 返回消息 /// </summary> [MessageHeader] public string Message { get; set; } /// <summary> /// 文件流 /// </summary> [MessageBodyMember] public Stream FileStream { get; set; } }
如果我们想传递多个参数我们可以在相对应的模型中新增自己的参数,这是因为我们的接口不可以定义其他参数,只能接收MessageContract定义的。那我们接口又该如何定义?不急,看下面的代码。
/// <summary> /// 上传文件 /// </summary> /// <param name="filestream">上传文件模型</param> /// <returns></returns> [OperationContract] UpFileResult UpLoadFile(UpFile filestream); /// <summary> /// 下载文件 /// </summary> /// <param name="downfile">下载文件模型</param> /// <returns></returns> [OperationContract] DownFileResult DownLoadFile(DownFile downfile);
我们在定义接口的时候需要在方法的头部定义一个协议--OperationContract,这样我们的接口才可访问,上方代码中的Result是我们的回调结果值。IDAL定义好了,接下来是接口实现,我们这边直接在BLL层对功能进行实现。这边的实现分为两部分,文件上传和文件下载。
文件上传我们可以根据自己项目的需要定义文件存储路径,注:在指定文件存放路径的时候需要判断文件夹是否存在。然后读取文件流保存,功能实现比较简单,这边直接贴出代码,需要说明的是我们filedata.DictoryName,这边是客户端定义的存储路径,项目需要以"客户代码/用户ID/"形式路径来存储文件。
/// <summary> /// 上传文件 /// </summary> /// <param name="filedata"></param> /// <returns></returns> public UpFileResult UpLoadFile(UpFile filedata) { UpFileResult result = new UpFileResult(); string _baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory; string path = @"\" + filedata.DictoryName + @"\"; string _Path = _baseDirectory + @"\" + string.Format(path, filedata.CustomerCode); if (!Directory.Exists(_Path)) { Directory.CreateDirectory(_Path); } byte[] buffer = new byte[filedata.FileSize]; FileStream fs = new FileStream(_Path + filedata.FileName, FileMode.Create, FileAccess.Write); int count = 0; while ((count = filedata.FileStream.Read(buffer, 0, buffer.Length)) > 0) { fs.Write(buffer, 0, count); } //清空缓冲区 fs.Flush(); //关闭流 fs.Close(); string filename = string.Empty; if (filedata.IsCopy) { try { string aLastName = filename.Substring(filename.LastIndexOf(".") + 1, (filename.Length - filename.LastIndexOf(".") - 1)); //扩展名 filename = DateTime.Now.ToString("yyyyMMddmmffff") + "." + aLastName + ""; File.Copy(_Path + filedata.FileName, _Path + filename); result.CopyFileName = filename; } catch (Exception) { result.CopyFileName = ""; } } result.IsSuccess = true; result.Message = path + filedata.FileName; return result; }文件的下载则是上传的逆过程,我们依然需要判断文件是否存在于服务目录。我们需要定义两个方法来实现下载功能,一个是单个文件下载,一个是将对应用户的所上传的文件打包压缩。正常我们客户端是判断用户本地缓存文件夹是否存在文件夹,如果没有存在直接请求下载整个压缩包,反之则判断对应的文件是否存在,不存在单独请求指定文件。这两者的文件下载请求是一样的,不一样的是文件打包压缩。文件下载的实现代码如下, 注:文件流位置需要置为0,不然客户端保存会出现问题。
/// <summary> /// 下载文件 /// </summary> /// <param name="filedata"></param> /// <returns></returns> public DownFileResult DownLoadFile(DownFile filedata) { DownFileResult result = new DownFileResult(); string path = System.AppDomain.CurrentDomain.BaseDirectory + @"\" + string.Format(filedata.FileName, filedata.CustomerCode); // string path = filedata.FileName; if (!File.Exists(path)) { result.IsSuccess = false; result.FileSize = 0; result.Message = "服务器不存在此文件"; result.FileStream = new MemoryStream(); return result; } Stream ms = new MemoryStream(); FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); fs.CopyTo(ms); ms.Position = 0; //重要,不为0的话,客户端读取有问题 result.IsSuccess = true; result.FileSize = ms.Length; result.FileStream = ms; fs.Flush(); fs.Close(); return result; }
文件解/压缩完整代码如下,这边的文件压缩只压缩指定文件夹目录下一级文件,如果指定文件夹内又有文件夹,那么这个文件夹下的文件不打包压缩。该帮助类需要引用ICSharpCode.SharpZipLib.dll类库。
/// <summary> /// 压缩帮助类 /// </summary> public class ZipHelper { #region 新创建压缩文件 /// <summary> /// 创建压缩文件 例如CreateZipFile(@"d:\", @"d:\a.zip"); /// </summary> /// <param name="filesPath">文件位置</param> /// <param name="zipFilePath">压缩后文件存放位置</param> public static bool CreateZipFile(string filesPath, string zipFilePath) { bool result = true; if (!Directory.Exists(filesPath)) { Console.WriteLine("Cannot find directory '{0}'", filesPath); result = false; } try { string[] filenames = Directory.GetFiles(filesPath); using (ZipOutputStream s = new ZipOutputStream(File.Create(zipFilePath))) { s.SetLevel(9); // 压缩级别 0-9 //s.Password = "123"; //Zip压缩文件密码 byte[] buffer = new byte[4096]; //缓冲区大小 foreach (string file in filenames) { ZipEntry entry = new ZipEntry(Path.GetFileName(file)); entry.DateTime = DateTime.Now; s.PutNextEntry(entry); using (FileStream fs = File.OpenRead(file)) { int sourceBytes; do { sourceBytes = fs.Read(buffer, 0, buffer.Length); s.Write(buffer, 0, sourceBytes); } while (sourceBytes > 0); } } s.Finish(); s.Close(); } result = true; } catch (Exception ex) { Console.WriteLine("Exception during processing {0}", ex); result = false; } return result; } #endregion #region 新解压压缩包 /// <summary> /// 解压压缩包 例如 UnZipFile(@"d:\a.zip"); /// </summary> /// <param name="zipFilePath">压缩包位置</param> private static bool UnZipFile(string zipFilePath) { bool result = true; if (!File.Exists(zipFilePath)) { Console.WriteLine("Cannot find file '{0}'", zipFilePath); result = false; } try { using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipFilePath))) { ZipEntry theEntry; while ((theEntry = s.GetNextEntry()) != null) { Console.WriteLine(theEntry.Name); string directoryName = Path.GetDirectoryName(theEntry.Name); string fileName = Path.GetFileName(theEntry.Name); // create directory if (directoryName.Length > 0) { Directory.CreateDirectory(directoryName); } if (fileName != String.Empty) { using (FileStream streamWriter = File.Create(theEntry.Name)) { int size = 2048; byte[] data = new byte[2048]; while (true) { size = s.Read(data, 0, data.Length); if (size > 0) { streamWriter.Write(data, 0, size); } else { break; } } } } } } result = true; } catch (Exception ex) { Console.WriteLine("错误信息 '{0}'", ex.Message); result = false; } return result; } #endregion }
请求下载压缩包的时候需要先判断是否压缩成功再执行操作,调用的方法如下:ZipHelper.CreateZipFile(path, zipedFile);path为需要压缩文件夹位置,zipedFile为压缩后文件存放位置[文件后缀名为.zip]。
至此我们的C#仿旺旺皮肤实现上半部分以介绍完毕,下一篇我们将介绍客户端的实现。