在Winform中上传文件的工具类-ResourceMgr

ResourceMgr是一个可扩展可定制的上传工具类,它提供上传进度和状态指示。配合可视化的组件,有类似于快车或迅雷下载软件的效果。

它的基类的完整实现如下:由于我是在应用程序整体框架中抠出来的代码,所以有些可能是不属于.net自带的类库中的方法。

using System;
using System.IO;
using System.ServiceModel;
using Aosu.Com.Utils;
using Bolan.com.FinancialEditor.Interfaces;
using Bolan.com.FinancialEditor.Utils;
using System.Diagnostics;
using System.Threading;
using System.Configuration;

namespace Bolan.com.FinancialEditor.ClassLibs
{
    /// <summary>
    /// 资源服务器管理对象,默认实现是WCF, 子类实现是HTTP带进度条
    /// </summary>
    public class ResourceMgr
    {
        private string _gateway = "";   // 应用程序网关
        private string _appName = "FinancialEditor";   // 应用程序名,表示资源在资源服务器上被集中管理的根路径。
        private string _category = "";  // 资源分类标识

        #region 记录线程状态(wang)
        /// <summary>
        /// 计时器,用于监控上传进度
        /// </summary>
        protected Stopwatch stopWatch = new Stopwatch();

        /// <summary>
        /// 上传线程
        /// </summary>
        protected Thread upThread;

        private string localFilePath;

        /// <summary>
        /// 唯一标识,和它对应的文章的OBJECTID保持一致,或者为空
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// 本地文件完全路径名
        /// </summary>
        public string LocalFilePath
        {
            get { return localFilePath; }
            set
            {
                localFilePath = value;
                FileInfo fi = new FileInfo(localFilePath);
                Total = fi.Length;
                FileName = fi.Name;
                FileCreateTime = fi.CreationTime;
                FileEditTime = fi.LastWriteTime;
            }
        }

        /// <summary>
        /// 不包括文件路径的文件名
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// 本地文件的创建时间
        /// </summary>
        public DateTime FileCreateTime { get; set; }

        /// <summary>
        /// 本地文件最后编辑的时间
        /// </summary>
        public DateTime FileEditTime { get; set; }

        /// <summary>
        /// 上传开始时间
        /// </summary>
        public DateTime StartTime { get; set; }

        /// <summary>
        /// 上传完成时间
        /// </summary>
        public DateTime FinishedTime { get; set; }

        /// <summary>
        /// 总字节数
        /// </summary>
        public long Total { get; set; }

        private long finished;
        /// <summary>
        /// 已上传完成的字节数
        /// </summary>
        public long Finished
        {
            get { return finished; }
            set
            {
                if (finished != value)
                {
                    finished = value;
                    if (ProgressChanged != null)
                    {
                        ProgressChanged(this);
                    }
                }
            }
        }

        /// <summary>
        /// 上传速率,字节/秒
        /// </summary>
        public double Speed { get; set; }

        /// <summary>
        /// 上传已用去的时间
        /// </summary>
        public TimeSpan Elapsed { get; set; }

        /// <summary>
        /// 上传剩余时间
        /// </summary>
        public TimeSpan Remaining { get; set; }

        private UploadStatus status;
        /// <summary>
        /// 指示处理状态的标志位
        /// </summary>
        public UploadStatus Status
        {
            get
            {
                if ((status < UploadStatus.完成中 && upThread != null && !upThread.IsAlive))
                {
                    upThread.Abort();
                    status = UploadStatus.等待中;
                }
                if ((status == UploadStatus.连接中 && Elapsed.TotalSeconds > 30 && upThread != null)) //长时间连接不上
                {
                    upThread.Abort();
                    status = UploadStatus.等待中;
                }

                return status;
            }
            set
            {
                if (status != value)
                {
                    status = value;
                    if (StatusChanged != null) StatusChanged(this);
                }
            }
        }

        /// <summary>
        /// 错误信息
        /// </summary>
        public string ErrorMessage { get; set; }

        public int FinishedPercent
        {
            get
            {
                return Total == 0 ? 0 : (int)(Finished * 100 / Total);
            }
        }

        public override string ToString()
        {
            return String.Format("{0}/{1}KB", Finished / 1024, Total / 1024);
        }

        /// <summary>
        /// 最后上传到的绝对URL地址
        /// </summary>
        public string ResultUrl { get; set; }


        public TransferAction ProgressChanged;
        public TransferAction StatusChanged;
        #endregion

        /// <summary>
        /// 应用程序网关
        /// </summary>
        public string Gateway
        {
            get { return _gateway; }
            set { _gateway = value; }
        }
        /// <summary>
        /// 应用程序名,表示资源在资源服务器上被集中管理的根路径
        /// </summary>
        public string AppName
        {
            get { return _appName; }
            set { _appName = value; }
        }
        /// <summary>
        /// 资源分类标识
        /// </summary>
        public string Category
        {
            get { return _category; }
            set { _category = value; }
        }

        /// <summary>
        /// 附加信息
        /// </summary>
        public object Tag
        {
            get;
            set;
        }

        /// <summary>
        /// 附加属性
        /// </summary>
        public string AddinProp { get; set; }

        protected FileTransferMessage transferMsg;
        protected FileTransferMessage TransferMsg
        {
            get
            {
                if (transferMsg == null) CreateUploadMsg();
                return transferMsg;
            }
        }

        /// <summary>
        /// 上传文件到资源服务器(同步方式)
        /// </summary>
        /// <param name="path">待上传资源的本地路径</param>
        /// <returns>返回资源的相对路径</returns>
        public string Upload(string path)
        {
            stopWatch.Restart();
            Elapsed = stopWatch.Elapsed;
            ErrorMessage = "";

            Status = UploadStatus.连接中;
            StartTime = DateTime.Now;
            LocalFilePath = path;
            CreateUploadMsg();
            try
            {
                DoUpload();
                Status = UploadStatus.上传完成;
                FinishedTime = DateTime.Now;
                Elapsed = stopWatch.Elapsed;
                Finished = Total;
                return ResultUrl;
            }
            catch (Exception ex)
            {
                FinishedTime = DateTime.Now;
                Elapsed = stopWatch.Elapsed;
                ErrorMessage = ex.Message;
                Status = UploadStatus.异常;
                return "";
            }
            finally
            {
                stopWatch.Stop();
            }
        }

        protected virtual void DoUpload()
        {
            ITransfer proxy = SvcChannel.CreateClient<ITransfer>(Gateway, "iUpload");
            proxy.TransferFile(TransferMsg);
            // 返回资源的相对路径。
        }

        public void UploadAsync()
        {
            Stop();
            upThread = new Thread(new ThreadStart(delegate()
            {
                Upload(LocalFilePath);
            }));

            upThread.Start();
        }

        protected static string UploadRootPath = ConfigurationManager.AppSettings["UploadFolder"];
        protected virtual void CreateUploadMsg()
        {
            Id = CommOp.NewId();
            // 没有指定上传根位置时返回错误
            if (string.IsNullOrEmpty(_appName))
            {
                throw new Exception("未指定上传资源所属的应用名,请与系统管理员联系!");
            }

            // 未指定资源的分类
            if (string.IsNullOrEmpty(_category))
            {
                throw new Exception("未指定上传资源所属的分类,请与系统管理员联系!");
            }

            // 得到文件信息对象
            FileInfo fileObject = new FileInfo(LocalFilePath);

            // 计算资源种子位置
            string strSeedPath = "";

            //王家新改,直接new guid避免上传相同文件无效的问题。
            string strSeedKey = CommOp.NewId();

            strSeedPath = string.Format("{0}\\{1}\\{2}",
                _appName,
                _category,
                strSeedKey.Substring(0, 2));


            // 创建文件传输消息对象。
            transferMsg = new FileTransferMessage();
            transferMsg.FileName = strSeedKey.Substring(2) + (((".".Equals(fileObject.Extension) || string.IsNullOrEmpty(fileObject.Extension))) ? "" : fileObject.Extension); // 文件名
            transferMsg.FileData = new FileStream(LocalFilePath, FileMode.Open, FileAccess.Read);    // 文件的数据流
            transferMsg.UploadFolder = strSeedPath;
            ResultUrl = new Uri(new Uri(Gateway), "upload\\" + transferMsg.UploadFolder.Replace('\\', '/') + transferMsg.FileName).ToString();
        }

        /// <summary>
        /// 停止上传动作
        /// </summary>
        public void Stop()
        {
            Status = UploadStatus.等待中;
            if (upThread != null && upThread.IsAlive)
            {
                upThread.Abort();
            }
        }
    }

    public enum UploadStatus
    {
        等待中 = 0,
        连接中,
        上传中,
        完成中,
        上传完成,
        处理完毕,
        异常,
    }

    /// <summary>
    /// 资源管理引发的动作
    /// </summary>
    /// <param name="mgr"></param>
    public delegate void TransferAction(ResourceMgr mgr);
}

另外,由于WCF实现的上传没有办法实现进度条,所以我在服务器端提供了常规的asp.net的上传方式:

与此对应的ResourceMgr需要扩展它的实现,因此产生了ResourceMgr2:

public class ResourceMgr2 : ResourceMgr
    {
        // <summary>
        /// 将本地文件上传到指定的服务器(HttpWebRequest方法)
        /// </summary>
        protected override void DoUpload()
        {
            string address = String.Format("{0}?app={1}&cat={2}&{3}&{4}", Gateway, AppName, Category, LoginMgr.Instance.VerifyQuery, AddinProp);
            //时间戳
            string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");
            byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + strBoundary + "\r\n");

            string strPostHeader = String.Format(@"--{0}
Content-Disposition: form-data; name=""file"" filename=""{1}""
Content-Type:application/octet-stream

", strBoundary, new FileInfo(LocalFilePath).Name);
            byte[] postHeaderBytes = Encoding.UTF8.GetBytes(strPostHeader);

            // 根据uri创建HttpWebRequest对象
            HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(new Uri(address));
            httpReq.Method = "POST";

            //对发送的数据不使用缓存
            httpReq.AllowWriteStreamBuffering = false;

            //设置获得响应的超时时间(3600秒)
            httpReq.Timeout = 3600000;
            httpReq.ContentType = "multipart/form-data; boundary=" + strBoundary;
            long fileLength = TransferMsg.FileData.Length;
            httpReq.ContentLength = TransferMsg.FileData.Length + postHeaderBytes.Length + boundaryBytes.Length; ;

            //每次上传64k
            int bufferLength = 65536;
            byte[] buffer = new byte[bufferLength];

            //已上传的字节数
            Finished = 0;

            Stream postStream = httpReq.GetRequestStream();

            int size;
            //发送请求头部消息
            postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
            TransferMsg.FileData.Seek(0, SeekOrigin.Begin);

            while ((size = TransferMsg.FileData.Read(buffer, 0, bufferLength)) > 0 && Status != UploadStatus.等待中)
            {
                postStream.Write(buffer, 0, size);
                Finished += size;
                Elapsed = stopWatch.Elapsed;
                Speed = Finished / Elapsed.TotalMilliseconds * 1000; //KB
                Remaining = new TimeSpan(0, 0, (int)((Total - Finished) / Speed));
                Speed /= 1024;
                if (Status == UploadStatus.连接中)
                {
                    Status = UploadStatus.上传中;
                }
            }

            if (Status == UploadStatus.等待中)
            {
                return;
            }

            Status = UploadStatus.完成中;
            //添加尾部的时间戳
            postStream.Write(boundaryBytes, 0, boundaryBytes.Length);
            postStream.Close();
            //获取服务器端的响应
            WebResponse webRespon = httpReq.GetResponse();
            //读取服务器端返回的消息
            ResultUrl = WebHelper.GetWebResponseText(webRespon);
            TransferMsg.FileData.Close();
        }

        protected override void CreateUploadMsg()
        {
            transferMsg = new Interfaces.FileTransferMessage();
            transferMsg.FileData = new FileStream(LocalFilePath, FileMode.Open, FileAccess.Read);    // 文件的数据流
        }
    }


对应的服务器端的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Configuration;
using Bolan.com.FinancialEditor.WebBaseClass;
using Bolan.com.FinancialEditor.Utils;

namespace Bolan.com.FinancialEditor
{
    /// <summary>
    /// HttpHandler上传文件
    /// 需要传入的参数: app=应用名&cat=分类名
    /// 当上传研报时补充:as=是否挂起(0/1)&cid=栏目id & id=文章ID(当文章ID不为空时,处理逻辑有所不同)
    /// 附加的身份验证信息(uid=用户ID&pwd=加密后的密码&mcode=机器码&sid=会话ID)
    /// </summary>
    public class FileUpload : BaseHandler
    {
        /// <summary>
        /// 上传文件保存到服务器的绝对路径
        /// </summary>
        public string FilePath;

        /// <summary>
        /// 上传文件的客户端文件名
        /// </summary>
        public string LocalFileName;

        /// <summary>
        /// 服务端返回的文件的完整URL
        /// </summary>
        public string RemoteFileUrl;

        /// <summary>
        /// 唯一标识,用来在存数据库时决定OBJECTID
        /// </summary>
        public string ID;

        string appName; //应用名
        string category; //分类名

        //服务器上传文件的根目录
        static string UPLOAD_ROOT_FOLDER = ConfigurationManager.AppSettings["UploadFolder"];

        //服务器上传文件的Web虚拟目录
        static string VIRTUAL_UPLOAD_ROOT = ConfigurationManager.AppSettings["VirtualUploadRoot"];

        protected override string GetOutput()
        {
            HttpPostedFile hpf = Request.Files[0];
            appName = Request.QueryString["app"];
            category = Request.QueryString["cat"];
            LocalFileName = hpf.FileName;
            string path = CreatePath();
            FilePath = Path.Combine(UPLOAD_ROOT_FOLDER, path);
            string uploadFolder = new FileInfo(FilePath).DirectoryName;
            // 创建保存文件夹
            if (!Directory.Exists(uploadFolder))
            {
                Directory.CreateDirectory(uploadFolder);
            }
            hpf.SaveAs(FilePath);

            RemoteFileUrl = new Uri(Request.Url, VIRTUAL_UPLOAD_ROOT + '/' + path.Replace('\\', '/')).ToString();
            if (!String.IsNullOrEmpty(category))
            {
                UploadAction.DoAction(category, this);
            }
            return RemoteFileUrl;
        }

        /// <summary>
        /// 生成服务端保存文件的路径
        /// </summary>
        /// <returns></returns>
        string CreatePath()
        {
            if (string.IsNullOrEmpty(appName))
            {
                throw new Exception("未指定上传资源所属的应用名,请与系统管理员联系!");
            }

            if (string.IsNullOrEmpty(category))
            {
                throw new Exception("未指定上传资源所属的分类,请与系统管理员联系!");
            }

            FileInfo fi = new FileInfo(LocalFileName);
            string rid = CommOp.NewId();

            //路径分四部分{分类管理路径}/{种子访问效率控制路径}/{资源保存名字部分}{扩展名}
            string path = string.Format("{0}\\{1}\\{2}\\{3}{4}",
                   appName,
                   category,
                   rid.Substring(0, 2),
                   rid.Substring(2),
                   (((".".Equals(fi.Extension) || string.IsNullOrEmpty(fi.Extension))) ? "" : fi.Extension));

            return path;
        }
    }
}


至于客户端的应用,我给出一个最简单的带进度条的上传对话框:

public partial class FormUploadProgress : FormGeneral
    {
        ResourceMgr resourceMgr;
        public FormUploadProgress(ResourceMgr mgr)
        {
            InitializeComponent();

            resourceMgr = mgr;

            mgr.ProgressChanged = (mgr1) =>
            {
                if (InvokeRequired)
                Invoke(new MethodInvoker(delegate
                {
                    progressBarControl1.Position = mgr1.FinishedPercent;
                }));
                else
                    progressBarControl1.Position = mgr1.FinishedPercent;
            };

            mgr.StatusChanged = (mgr1) =>
            {
                if (InvokeRequired)
                {
                    Invoke(new MethodInvoker(EndCall));
                }
                else
                    EndCall();
            };
        }

        void EndCall()
        {
            if (resourceMgr.Status == UploadStatus.上传完成)
                DialogResult = DialogResult.OK;
            else if (resourceMgr.Status == UploadStatus.异常)
            {
                btnRetry.Enabled = true;
                lblUploadTips.Text = resourceMgr.ErrorMessage;
            }
        }

        protected override void SetFormAttributes()
        {
            LoadAccessOnStart = false;
            ShowSplashOnStart = false;
        }

        protected override void FormInit()
        {
            resourceMgr.Status = UploadStatus.等待中;
            lblUploadTips.Text = "正在上传 " + resourceMgr.LocalFilePath;
            resourceMgr.UploadAsync();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            resourceMgr.Stop();
        }

        private void btnRetry_Click(object sender, EventArgs e)
        {
            FormInit();
        }
    }


运行效果如下:

在Winform中上传文件的工具类-ResourceMgr

 

当然,你可以把若干个ResourceMgr放在一个IList集合里,作为数据源给网络控件,这样就可以显示多个文件同时上传的进度。

 

另外,它的服务端实现也可扩展,主要扩展点是

服务端的:

 if (!String.IsNullOrEmpty(category))
 {
        UploadAction.DoAction(category,
this);
 }
这里调用了UploadAction.DoAction()方法,用于执行成功上传完成以后,服务端要完成的善后工作,可能包括留痕、写库或其他业务操作。

UploadAction是一个抽象的基类,它定义如下:

 

/// <summary>
    /// 定义文件上传后需要执行的业务的基类
    /// </summary>
    public abstract class UploadAction
    {
        /// <summary>
        /// 执行上传后的行为
        /// </summary>
        /// <param name="handler"></param>
        public abstract void DoAction(FileUpload handler);

        //生成各上传后业务对象的抽象工厂,为简单,直接写死。今后可以改用配置文件
        public static UploadActionFactory Factory
        {
            get { return new UploadActionFactory(); }
        }

        public static void DoAction(string cat, FileUpload handler)
        {
            var action = Factory.CreateAction(cat);
            if (action != null) action.DoAction(handler);
        }
    }

 

它的子类可以实现具体的DoAction方法,用于实现具体的业务操作。然后由主调程序通过抽象工厂来瘊定调用哪个UploadAction.

抽象工厂的定义如下:

/// <summary>
    /// 工厂类:生成上传后要执行的业务对象
    /// </summary>
    public class UploadActionFactory
    {
        /// <summary>
        /// 生成上传操作后要执行的业务
        /// </summary>
        /// <param name="act">动作标识符</param>
        /// <returns>上传后的业务对象</returns>
        public UploadAction CreateAction(string act)
        {
            switch (act)
            {
                case "Files": return new ReportAction();
                default: return null;
            }
        }
    }

 

 当然,如果只是单纯的上传文件,而没有更多要求,以上的服务端扩展可以无视。

你可能感兴趣的:(在Winform中上传文件的工具类-ResourceMgr)