asp.net文件上传功能(单文件,多文件,自定义生成缩略图,水印)

前言

  上传功能,是大家经常用到了,可能每一个项目都可以会用到。网上到处都有上传功能的代码。比我写的好的有很多。我这里也仅是分享我的代码。

  功能实现点

  1.单个文件上传;

  2.多个文件上传;

  3.对于图片等类型的图像,可以自定义生成缩略图大小;

  4.文件服务器扩展。

  模式

  主要使用的是“模板方法”的设计模式。

  本文章的功能优缺点

  1.可以自定义生成缩略图的大小,任意定义。对于像微生活运动户外商城(http://sports.8t8x.com/) 、淘宝网等的网站,他们需要上传大量的商品图片时,非常有用。

  2.缺点,我对System.Drawing的命名空间不太熟练,生成图像的方法还是从网上抄的,我觉得我自己得到的这些生成图像的方法,不是非常好。

  代码实现

  1.接口定义

  

复制代码 代码如下:

  using System;

  using System.Collections.Generic;

  using System.Linq;

  using System.Text;

  using System.Web;

  namespace CCNF.Plugin.Upload

  {

  /// <summary>

  /// 上传功能的接口

  /// </summary>

  /// <creator>Marc</creator>

  public interface IUpload

  {

  /// <summary>

  /// 上传单个文件。

  /// </summary>

  /// <param name="sourcefile"></param>

  /// <returns></returns>

  /// <author>Marc</author>

  int SaveAs(HttpPostedFile sourcefile);

  }

  }

  2.抽象模板方法类

  由于使用代码插入的方式,cnblogs会报错,所以, 我不得不使用原始的copy方式,可能看起来会不太舒服。

  

复制代码 代码如下:

  using System;

  using System.Collections.Generic;

  using System.Text;

  using System.Configuration;

  using System.IO;

  using System.Net;

  using System.Data;

  using System.Drawing;

  using System.Drawing.Imaging;

  using System.Drawing.Drawing2D;

  using System.Web;

  using System.Collections;

  namespace CCNF.Plugin.Upload

  {

  /// <summary>

  /// 上传功能。

  /// 本类提供上传的一般性方法。

  /// </summary>

  /// <creator>Marc</creator>

  public abstract class UploadAbstract : IUpload

  {

  #region 常量属性

  /// <summary>

  /// 允许上传的文件扩展名。

  /// 多个文件扩展名以英文逗号隔开。

  /// 默认从Web.config中获取。

  /// </summary>

  private readonly string UPLOADEXTENTION = ConfigurationManager.AppSettings["UploadExtention"];

  private string uploadExtention = null;

  /// <summary>

  /// 允许上传的文件扩展名。

  /// 多个文件扩展名以英文逗号隔开。

  /// 默认从Web.config中获取。

  /// </summary>

  public string UploadExtention

  {

  get

  {

  if (string.IsNullOrEmpty(this.uploadExtention))

  {

  if (string.IsNullOrEmpty(UPLOADEXTENTION))

  {

  throw new Exception("web.config中未配置UploadExtention属性");

  }

  this.uploadExtention = UPLOADEXTENTION;

  }

  return this.uploadExtention;

  }

  set

  {

  this.uploadExtention = value;

  }

  }

  /// <summary>

  /// 允许上传的单个文件最大大小。

  /// 单位为k。

  /// 默认从Web.config中获取。

  /// </summary>

  private readonly int UPLOADLENGTH = Convert.ToInt16(ConfigurationManager.AppSettings["UploadLength"]);

  private int uploadLength = 0;

  /// <summary>

  /// 允许上传的单个文件最大大小。

  /// 单位为。

  /// 默认从Web.config中获取。

  /// </summary>

  public int UploadLength

  {

  get

  {

  if (this.uploadLength == 0)

  {

  this.uploadLength = UPLOADLENGTH;

  }

  return this.uploadLength;

  }

  set

  {

  this.uploadLength = value;

  }

  }

  /// <summary>

  /// 所上传的文件要保存到哪个物理盘上。

  /// 此值为严格的物理文件夹路径。如:E:\CCNF\

  /// 注意:必须有盘符。

  /// 此属于用于扩展图片服务器数据存储。

  /// 默认从Web.config中获取。

  /// </summary>

  private readonly string UPLOADPHYSICALPATH = ConfigurationManager.AppSettings["UploadPhysicalPath"];

  private string uploadPhysicalPath = null;

  /// <summary>

  /// 所上传的文件要保存到哪个物理盘上。

  /// 此值为严格的物理文件夹路径。如:E:\CCNF\

  /// 注意:必须有盘符。

  /// 此属性用于扩展图片服务器数据存储。

  /// 默认从Web.config中获取。

  /// </summary>

  public string UploadPhysicalPath

  {

  get

  {

  if (string.IsNullOrEmpty(this.uploadPhysicalPath))

  {

  if (string.IsNullOrEmpty(UPLOADPHYSICALPATH))

  {

  throw new Exception("web.config中未配置UploadPhysicalPath属性");

  }

  this.uploadPhysicalPath = UPLOADPHYSICALPATH;

  }

  return this.uploadPhysicalPath;

  }

  set

  {

  this.uploadPhysicalPath = value;

  }

  }

  #endregion

  #region 枚举

  /// <summary>

  /// 水印类型

  /// </summary>

  public enum WatermarkTypeEnum

  {

  /// <summary>

  /// 文字水印

  /// </summary>

  String = 1,

  /// <summary>

  /// 图片水印

  /// </summary>

  Image = 2

  }

  /// <summary>

  /// 上传结果

  /// </summary>

  protected enum UploadResultEnum

  {

  /// <summary>

  /// 未指定要上传的对象

  /// </summary>

  UploadedObjectIsNull = -9,

  /// <summary>

  /// 文件扩展名不允许

  /// </summary>

  ExtentionIsNotAllowed = -2,

  /// <summary>

  /// 文件大小不在限定范围内

  /// </summary>

  ContentLengthNotWithinTheScope = -1,

  /// <summary>

  /// 未配置或未指定文件的物理保存路径

  /// </summary>

  UploadPhysicalPathNoSpecify = -20,

  /// <summary>

  /// 未指定图片水印的相对文件物理路径

  /// </summary>

  ImageWartermarkPathNoSpecify = -30,

  /// <summary>

  /// 未指定水印的文字

  /// </summary>

  StringWatermarkNoSpecify = -31,

  /// <summary>

  /// 上传原始文件失败

  /// </summary>

  UploadOriginalFileFailure = 0,

  /// <summary>

  /// 生成缩略失败

  /// </summary>

  CreateThumbnailImageFailure = -3,

  /// <summary>

  /// 未知错误

  /// </summary>

  UnknownError = -4,

  /// <summary>

  /// 上传成功

  /// </summary>

  Success = 1

  }

  #endregion

  #region 上传属性

  /// <summary>

  /// 保存文件夹。

  /// 格式形如: upload\ 或 images\ 或 upload\user\ 等。以\结尾。

  /// 不允许加盘符

  /// </summary>

  public string SaveFolder { get; set; }

  /// <summary>

  /// 自定义生成新的文件夹。

  /// 格式形如: upload\ 或 images\ 或 upload\2011\10\8\ 等。以\结尾。

  /// 最终的文件夹 = UploadPhysicalPath + SaveFolder + Folder

  /// </summary>

  public string Folder { get; set; }

  /// <summary>

  /// 是否生成水印。

  /// 默认不启用水印生成。

  /// </summary>

  public bool IsMakeWatermark { get; set; }

  private int watermarkType = (int)WatermarkTypeEnum.String;

  /// <summary>

  /// 生成水印的方式:string从文字生成,image从图片生成。

  /// 默认是文字水印

  /// </summary>

  public int WatermarkType

  {

  get

  {

  return this.watermarkType;

  }

  set

  {

  this.watermarkType = value;

  }

  }

  /// <summary>

  /// 水印文字。

  /// </summary>

  public string Watermark { get; set; }

  /// <summary>

  /// 水印图片的位置。

  /// 提供图片水印的相对位置。

  /// 不含盘符。

  /// </summary>

  public string ImageWartermarkPath { get; set; }

  /// <summary>

  /// 上传后生成的新文件路径。

  /// 此路径为相对物理路径,不含盘符。

  /// </summary>

  public string NewFilePath { get; protected set; }

  /// <summary>

  /// 生成缩略图片的长宽, 是一个二维数据。 

  /// 如:int a[3,2]={{1,2},{5,6},{9,10}}。

  /// 如果上传的文件是图片类型,并且希望生成此图片的缩略图,那么请将需要生成的长宽尺寸以数组的方式保存到这里。

  /// </summary>

  public int[,] PicSize { get; set; }

  /// <summary>

  /// 生成的图片地址,与二维数组PicSize的长度是对应的。

  /// 如果有传入PicSize的数组,那么上传完毕后,系统会返回PicPath数组。

  /// 这个数组是它的最终上传路径,以便用户对此值进行数据库操作。

  /// 产生的PicPath的索引位置与PicSize是一一对应的。

  /// 此属性已声明为只读属性。

  /// </summary>

  public string[] PicPath { get; protected set; }

  #endregion

  /// <summary>

  /// 上传单个文件

  /// </summary>

  /// <param name="sourcefile"></param>

  /// <param name="upload"></param>

  /// <returns></returns>

  /// <author>Marc</author>

  public virtual int SaveAs(HttpPostedFile sourcefile)

  {

  int result = 0;

  //未知错误

  UploadResultEnum uploadResultEnum = UploadResultEnum.UnknownError;

  if (sourcefile == null)

  {

  //未指定要上传的对象

  uploadResultEnum = UploadResultEnum.UploadedObjectIsNull;

  }

  else

  {

  uploadResultEnum = Upload(sourcefile);

  }

  result = (int)uploadResultEnum;

  return result;

  }

  /// <summary>

  /// 上传文件

  /// </summary>

  /// <param name="sourcefile"></param>

  /// <returns></returns>

  /// <author>Marc</author>

  private UploadResultEnum Upload(HttpPostedFile sourcefile)

  {

  #region 上传前检测

  if (string.IsNullOrEmpty(UploadPhysicalPath))

  {

  //未配置或未指定文件的物理保存路径

  return UploadResultEnum.UploadPhysicalPathNoSpecify;

  }

  string fileName = sourcefile.FileName;

  string fileExtention = Path.GetExtension(fileName);

  if (!CheckExtention(fileExtention))

  {

  //文件扩展名不允许

  return UploadResultEnum.ExtentionIsNotAllowed;

  }

  int fileLength = sourcefile.ContentLength;

  if (fileLength <= 0 || fileLength > UploadLength * 1024)

  {

  //文件大小不在限定范围内

  return UploadResultEnum.ContentLengthNotWithinTheScope;

  }

  //创建要保存的文件夹

  string physicalPath = UploadPhysicalPath + SaveFolder + Folder;

  if (!Directory.Exists(physicalPath))

  {

  Directory.CreateDirectory(physicalPath);

  }

  #endregion

  string newFileName = "Ori_" + Guid.NewGuid().ToString() + fileExtention;

  //新文件相对物理路径

  string newFilePath = physicalPath + newFileName;

  #region 保存原始文件

  if (!IsMakeWatermark) //不启用水印时

  {

  //保存文件

  sourcefile.SaveAs(newFilePath);

  }

  else //要求生成水印

  {

  Image bitmap = new System.Drawing.Bitmap(sourcefile.InputStream);

  Graphics g = Graphics.FromImage(bitmap);

  g.InterpolationMode = InterpolationMode.High;

  g.SmoothingMode = SmoothingMode.AntiAlias;

  Image fromImg = null;

  try

  {

  //清除整个绘图面并以透明背景色填充

  //g.Clear(Color.Transparent);

  if (WatermarkType == (int)WatermarkTypeEnum.String) //生成文字水印

  {

  if (string.IsNullOrEmpty(Watermark))

  {

  //未指定水印的文字

  return UploadResultEnum.StringWatermarkNoSpecify;

  }

  Color color = Color.FromArgb(120, 255, 255, 255);

  Brush brush = new SolidBrush(color);

  // 自动根据界面来缩放字体大小

  int desiredWidth = (int)(bitmap.Width * .5);

  Font font = new Font("Arial", 16, FontStyle.Regular);

  SizeF stringSizeF = g.MeasureString(Watermark, font);

  float Ratio = stringSizeF.Width / font.SizeInPoints;

  int requiredFontSize = (int)(desiredWidth / Ratio);

  // 计算图片中点位置

  Font requiredFont = new Font("Arial", requiredFontSize, FontStyle.Bold);

  stringSizeF = g.MeasureString(Watermark, requiredFont);

  int x = desiredWidth - (int)(stringSizeF.Width / 2);

  int y = (int)(bitmap.Height * .5) - (int)(stringSizeF.Height / 2);

  g.DrawString(Watermark, new Font("Verdana", requiredFontSize, FontStyle.Bold), brush, new Point(x, y));

  bitmap.Save(newFilePath, ImageFormat.Jpeg);

  }

  else if (WatermarkType == (int)WatermarkTypeEnum.Image) //生成图片水印

  {

  if (string.IsNullOrEmpty(ImageWartermarkPath))

  {

  //未指定图片水印的相对文件物理路径

  return UploadResultEnum.ImageWartermarkPathNoSpecify;

  }

  fromImg = Image.FromFile(ImageWartermarkPath);

  g.DrawImage(fromImg, new Rectangle(bitmap.Width - fromImg.Width, bitmap.Height - fromImg.Height, fromImg.Width, fromImg.Height), 0, 0, fromImg.Width, fromImg.Height, GraphicsUnit.Pixel);

  bitmap.Save(newFilePath, ImageFormat.Jpeg);

  }

  }

  catch

  {

  //上传原始文件失败

  return UploadResultEnum.UploadOriginalFileFailure;

  }

  finally

  {

  if (fromImg != null)

  {

  fromImg.Dispose();

  }

  g.Dispose();

  bitmap.Dispose();

  }

  }

  #endregion

  this.NewFilePath = newFilePath.Replace("\\", "/");

  #region 生成各种规格的缩略图

  //生成各种规格的缩略图

  if (PicSize != null && PicSize.Length > 0)

  {

  int width, height;

  ArrayList picPathArray = new ArrayList();

  for (int i = 0; i < PicSize.GetLength(0); i++)

  {

  width = PicSize[i, 0];

  height = PicSize[i, 1];

  Guid tempGuid = Guid.NewGuid();

  //缩略图名称

  string thumbnailFileName = physicalPath + tempGuid.ToString() + "_" + width.ToString() + "X" + height.ToString() + fileExtention;

  if (!SaveThumb(sourcefile, width, height, thumbnailFileName))

  {

  //生成缩略失败

  return UploadResultEnum.CreateThumbnailImageFailure;

  }

  picPathArray.Add(thumbnailFileName.Replace("\\", "/"));

  }

  PicPath = (string[])picPathArray.ToArray(typeof(string));

  }

  #endregion

  //上传成功

  return UploadResultEnum.Success;

  }

  /// <summary>

  /// 生成缩略图

  /// </summary>

  /// <param name="sourcefile"></param>

  /// <param name="width">生成缩略图的宽度</param>

  /// <param name="height">生成缩略图的高度</param>

  /// <param name="thumbnailFileName">缩略图生成路径,含盘符的绝对物理路径。</param>

  /// <returns></returns>

  /// <author>Marc</author>

  private bool SaveThumb(HttpPostedFile sourcefile, int width, int height, string thumbnailFileName)

  {

  bool result = false;

  Image ori_img = Image.FromStream(sourcefile.InputStream);

  int ori_width = ori_img.Width;//650

  int ori_height = ori_img.Height;//950

  int new_width = width;//700

  int new_height = height;//700

  // 在此进行等比例判断,公式如下:

  if (new_width > ori_width)

  {

  new_width = ori_width;

  }

  if (new_height > ori_height)

  {

  new_height = ori_height;

  }

  if ((double)ori_width / (double)ori_height > (double)new_width / (double)new_height)

  {

  new_height = ori_height * new_width / ori_width;

  }

  else

  {

  new_width = ori_width * new_height / ori_height;

  }

  Image bitmap = new System.Drawing.Bitmap(new_width, new_height);

  Graphics g = Graphics.FromImage(bitmap);

  try

  {

  g.SmoothingMode = SmoothingMode.HighQuality;

  g.InterpolationMode = InterpolationMode.High;

  g.Clear(System.Drawing.Color.Transparent);

  g.DrawImage(ori_img, new Rectangle(0, 0, new_width, new_height), new Rectangle(0, 0, ori_width, ori_height), GraphicsUnit.Pixel);

  bitmap.Save(thumbnailFileName, ImageFormat.Jpeg);

  result = true;

  }

  catch

  {

  result = false;

  }

  finally

  {

  if (ori_img != null)

  {

  ori_img.Dispose();

  }

  if (bitmap != null)

  {

  bitmap.Dispose();

  }

  g.Dispose();

  bitmap.Dispose();

  }

  return result;

  }

  /// <summary>

  /// 检查文件扩展名

  /// </summary>

  /// <param name="extention"></param>

  /// <returns></returns>

  protected bool CheckExtention(string extention)

  {

  bool b = false;

  string[] extentions = this.UploadExtention.Split(',');

  for (int i = 0; i < extentions.Length; i++)

  {

  if (extention.ToLower() == extentions[i].ToLower())

  {

  b = true;

  break;

  }

  }

  return b;

  }

  /// <summary>

  /// 返回上传结果

  /// </summary>

  /// <param name="result">消息集合</param>

  /// <returns></returns>

  /// <author>marc</author>

  public string Error(int[] result)

  {

  string s = "";

  for (int i = 0; i < result.Length; i++)

  {

  s += "第" + (i + 1).ToString() + "张:";

  if (result[i] != 1)

  {

  switch (result[i])

  {

  case (int)UploadResultEnum.UploadedObjectIsNull:

  s += "未指定要上传的对象";

  break;

  case (int)UploadResultEnum.ExtentionIsNotAllowed:

  s += "文件扩展名不允许";

  break;

  case (int)UploadResultEnum.ContentLengthNotWithinTheScope:

  s += "文件大小不在限定范围内";

  break;

  case (int)UploadResultEnum.UploadPhysicalPathNoSpecify:

  s += "未配置或未指定文件的物理保存路径";

  break;

  case (int)UploadResultEnum.ImageWartermarkPathNoSpecify:

  s += "未指定图片水印的相对文件物理路径";

  break;

  case (int)UploadResultEnum.StringWatermarkNoSpecify:

  s += "未指定水印的文字";

  break;

  case (int)UploadResultEnum.UploadOriginalFileFailure:

  s += "上传原始文件失败";

  break;

  case (int)UploadResultEnum.CreateThumbnailImageFailure:

  s += "生成缩略失败";

  break;

  case (int)UploadResultEnum.UnknownError:

  s += "未知错误";

  break;

  default:

  break;

  }

  }

  else

  {

  s += "上传成功";

  }

  s += ";\\r\\n";

  }

  return s;

  }

  /// <summary>

  /// 返回上传结果

  /// </summary>

  /// <param name="result">消息值,int型</param>

  /// <returns></returns>

  /// <author>marc</author>

  public string Error(int result)

  {

  string s = null;

  switch (result)

  {

  case (int)UploadResultEnum.UploadedObjectIsNull:

  s += "未指定要上传的对象";

  break;

  case (int)UploadResultEnum.ExtentionIsNotAllowed:

  s += "文件扩展名不允许";

  break;

  case (int)UploadResultEnum.ContentLengthNotWithinTheScope:

  s += "文件大小不在限定范围内";

  break;

  case (int)UploadResultEnum.UploadPhysicalPathNoSpecify:

  s += "未配置或未指定文件的物理保存路径";

  break;

  case (int)UploadResultEnum.ImageWartermarkPathNoSpecify:

  s += "未指定图片水印的相对文件物理路径";

  break;

  case (int)UploadResultEnum.StringWatermarkNoSpecify:

  s += "未指定水印的文字";

  break;

  case (int)UploadResultEnum.UploadOriginalFileFailure:

  s += "上传原始文件失败";

  break;

  case (int)UploadResultEnum.CreateThumbnailImageFailure:

  s += "生成缩略失败";

  break;

  case (int)UploadResultEnum.UnknownError:

  s += "未知错误";

  break;

  case (int)UploadResultEnum.Success:

  s += "上传成功";

  break;

  default:

  break;

  }

  return s;

  }

  }

  }

  所有的实现功能都在这个模板方法中。

  主要定义了“常量属性”、“枚举”、“上传属性”, 以及开放方法 SaveAs(HttpPostedFile sourcefile) 和 返回错误消息的方法。

  3.具体抽象类的实现,很简单,请看

  

复制代码 代码如下:

  using System;

  using System.Collections.Generic;

  using System.Linq;

  using System.Text;

  using System.Web;

  namespace CCNF.Plugin.Upload

  {

  /// <summary>

  /// 上传文件

  /// </summary>

  /// <creator>Marc</creator>

  public sealed class Upload : UploadAbstract

  {

  }

  }

  4.前台处理页面Upload.ashx,注意是处理页面,ashx文件。

  

复制代码 代码如下:

  <%@ WebHandler Language="C#" Class="Upload" %>

  using System;

  using System.Web;

  using System.IO;

  public class Upload : IHttpHandler

  {

  public void ProcessRequest(HttpContext context)

  {

  HttpResponse response = context.Response;

  HttpRequest request = context.Request;

  HttpServerUtility server = context.Server;

  context.Response.ContentType = "text/plain";

  HttpPostedFile httpPostedFile = request.Files["FileData"];

  string uploadpath = request["folder"] + "\\";// server.MapPath(request["folder"] + "\\");

  if (httpPostedFile != null)

  {

  CCNF.Plugin.Upload.Upload upload = new CCNF.Plugin.Upload.Upload();

  upload.SaveFolder = uploadpath;

  upload.PicSize = new int[,] { { 200, 150 } };//生成缩略图,要生成哪些尺寸规格的缩略图,请自行定义二维数组

  int result = upload.SaveAs(httpPostedFile);

  if (result == 1)

  {

  context.Response.Write("1");

  }

  else

  {

  throw new Exception(upload.Error(result));

  // context.Response.Write("0");

  }

  }

  }

  public bool IsReusable

  {

  get

  {

  return false;

  }

  }

  }

  5.前台aspx页面调用ashx页面

  这里,我使用了一个第三方控件,因为这不是我本文的重点,所以,只简略说一下这个控件的名字是:jquery.uploadify,各位可以去找一下这个js控件的代码。使用起来很简单的的一个js封装控件。

  通过这个js控件,会将页面上传的文件post到ashx处理文件中,ashx会接收数据并开始上传。

  后记

  一、上传功能遍地都是, 而本文主要的亮点在于自定义生成多个缩略图。这对于像微生活运动户外商城(http://sports.8t8x.com/) 、淘宝网等的网站,非常适用。

  二、我再次回顾设计模式,温故而知新,又有新的发现,所以,发此文,聊表慰藉。

  关于本文作者

  马志远(Marc),1981年,2002年湖北大学肄业,现蜗居广州。2004年学习编程,至今已经有8年的编程经验,长期从事asp.net B/S方面的开发和设计。在项目解决方案上、在项目性能和扩展等上,具有深强的能力。您可以使用[email protected]与我取得联系。