我的程序我做主
~天~注~定~
posts - 5,comments - 38,trackbacks - 0

说到C#.NET的更动更新 大家都想到了ClickOnce,但很多时候它的功能并没有我们需要的足够的强大。其实它的原理很简单,为什么我们不自己开发一套呢?下面以我的开发实例与大家交流一下。

原理:
1.服务器有一虚拟目录Update,里面放置客户端的所有程序(由于IIS限制,不能升级.config文件,如需要则改IIS相应配置)
2.Update目录里再放置一Default.aspx文件,用来取出当前文件夹下的文件列表和文件修改时间,并形成一个XML返回出来。
3.客户端升级程序使用HttpWebRequest对象访问这个http://server/Update/Default.asp,并获得返回的XML。将XML解析后与本地文件时间比较,如果不同则下载。

代码:
(Update/Default.aspx文件)
<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>

<%
    SortedList FileList = new SortedList();
    string UpdatePath = AppDomain.CurrentDomain.BaseDirectory + "\\update\\";

        DirectoryInfo di = new DirectoryInfo(UpdatePath);
        foreach (FileInfo fi in di.GetFiles())
        {
           // 判断如果是Default.aspx则不下载到客户端
            if (fi.Name == "Default.aspx") continue;
            FileList.Add(fi.Name, fi.LastWriteTime.ToString());
        }

    string retValue = "<xml>";
    for (int i = 0; i < FileList.Count; i++)
    {
            retValue += "<row File='" + FileList.GetKey(i).ToString() + "' Date='" + FileList.GetValue(i).ToString() + "' />";
    }
    retValue += "</xml>";

    Response.Write(retValue);
%>

好,上面的代码很简单,就是获得服务器更新目录里的所有文件列表与文件的修改时间返回出来。

客户端检查更新的代码:
引用:
using System.Reflection;
using System.Collections;
using System.ComponentModel;
using System.Text;
using System.Xml;
using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Threading;

全局变量:
private SortedList DirectoryList = new SortedList();
private int m_FileNum = 1;


// 函数fnDoUpdate
private void fnDoUpdate()
{
      try
        {
                HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create("http://server/update/Default.aspx");
                HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
                Stream ReceiveStream = myResponse.GetResponseStream();
                Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
                StreamReader sr = new StreamReader(ReceiveStream, encode);
                Char[] read = new Char[256];
                string strResult = "";
                int count = sr.Read(read, 0, 256);
                while (count > 0)
                {
                    String str = new String(read, 0, count);
                    strResult += str;
                    count = sr.Read(read, 0, 256);
                }
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(strResult);
    // 这个Resource是自己定义的一个类  基本具有下面几个属性:Name,Url,LastModified 我就不定义了
                Resource tempResource;
                foreach (XmlNode xn in doc.SelectNodes("//row"))
                {
                    tempResource = new Resource();
                    tempResource.Name = xn.Attributes["File"].Value;
                    tempResource.Url = ClientConfiguration.AppServerUrl + "update/" + tempResource.Name;
                    tempResource.LastModified = Convert.ToDateTime(xn.Attributes["Date"].Value);
                    DirectoryList.Add(tempResource.Url, tempResource);
                }
// 好了 DirectoryList就是服务器的文件列表了 下面开始与本地文件进行比较了
                for (int i = 1; i <= DirectoryList.Count; i++)
                {
                    Resource currentResource = (Resource)DirectoryList.GetByIndex(i - 1);
                    string newFilePath = Application.StartupPath + "\\" + currentResource.Name;
// 删除掉时间相同的 因为不需要下载 这里用到了CheckDateTime函数是我们自己定义的 后面符带了
                    if (File.Exists(newFilePath) && CheckDateTime(currentResource.LastModified, LastModFromDisk(newFilePath)))
                        DownloadList.Remove(currentResource.Url);
               }

// 好了 到这里就得到了 我们需要更新的文件列表 DownloadList  开始下载了
// 下载过程的处理比较麻烦 使用多线程要出问题 我研究了很久想出这个办法
                m_FileNum = 1;  // 这个用来定位当前下载第几个文件
                progressBar1.EditValue = 0; //进度条规0
                progressBar1.Properties.Maximum = DownloadList.Count;
                StartDownload();
            }
      catch (Exception ex)
      {
              // 异常处理
       }
}

// 函数StartDownload 用于下载单个文件
        private void StartDownload()
        {
            try
            {
                Resource currentResource = (Resource)DownloadList.GetByIndex(m_FileNum - 1);
                string newFilePath = Application.StartupPath +"\\"+ currentResource.Name;
                if (File.Exists(newFilePath)) File.Delete(newFilePath);
                progressBar1.EditValue = m_FileNum ;
                WebClient client = new WebClient();
                client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
                client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
                Uri dUri = new Uri(currentResource.Url);
                client.DownloadFileAsync(dUri, newFilePath);
            }
            catch (Exception ex)
            {
                // 异常处理
            }
        }

// 下面二个事件 是WebClient对象的下载事件
        private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs args)
        {
                // 要做得漂亮点 这里放些计算速度的 或时时显示下载进度的处理
               // 常用的有:args.ProgressPercentage 下载进度百分比 args.BytesReceived 已下载字节数
               // ts.TotalMilliseconds 总字节数
        }

        private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs args)
        {
            Resource currentResource = (Resource)DownloadList.GetByIndex(m_FileNum - 1);
            string newFilePath = Application.StartupPath +"\\"+ currentResource.Name;
            // 将下载回来的文件修改时间更正 WebClient不会做这事只有我们来做了
            FileInfo f = new FileInfo(newFilePath);
            f.LastWriteTime = currentResource.LastModified;

            m_FileNum++;
            if (m_FileNum > DownloadList.Count)
            {
                    // 文件更新完成 这里写完成后的处理
            }
            else
            {
                // 没下完则继续下载下一个
                StartDownload();
            }
}

// 函数 LastModFromDisk 获取本地文件修改时间
  private DateTime LastModFromDisk(string filePath)
  {
   FileInfo f = new FileInfo(filePath);
   return (f.LastWriteTime);
  }


// 好了 到这里需要做的都写完了 下面是调用了 一定要用多线程来调用
                Thread t = new Thread(new ThreadStart(fnDoUpdate));
                t.Start();


好了,简单的自动更新功能就这么简单的完成了,你可以根据需要修改其中任意环节为自己想要的。
上面的代码是我在复杂的代码中把复杂的部分去掉了,也许你直接COPY过去用会编译不过,但我想
应该容易修改正确,希望大家多多指正。

大家不要置疑这思路的正确性,因为我开发的这段代码正在我公司的产品中使用,它的最大好处就是
我们可以随心所欲的修改下载的判断,或增加不同的需求。

posted on 2007-06-28 16:50 王学军 阅读(3275) 评论(32)  编辑 收藏 网摘

FeedBack:
#1楼[楼主]
2007-06-28 17:07 | 王学军      
第一次写博客 大家如果觉得有用请给支持一下哈
  回复  引用  查看    
2007-06-28 17:11 | MYOOP      
顶!!
写的不错。哈哈

  回复  引用  查看    
2007-06-28 17:34 | 曲滨*銘龘鶽      
不错精神不错

不过没考虑安全性,如果服务端的被感染
尤其现在 AV 、熊猫什么的

ClickOnce 有这方面的效验......

呵呵呵呵

  回复  引用  查看    
#4楼[楼主]
2007-06-28 17:46 | 王学军      
如果连服务器的文件都有病毒 那还有什么搞头~~~

何况我们需要更新的文件都是刚编译出来的。

再说服务器上还有杀毒软件哈

  回复  引用  查看    
2007-06-28 18:07 | 不错[未注册用户]
而且是很不错。
  回复  引用    
2007-06-28 19:39 | Anders.Zhao      
很不错的,支持博主
  回复  引用  查看    
2007-06-28 20:30 | jhtchina      
支持

  回复  引用  查看    
2007-06-28 21:17 | orichisonic[未注册用户]
这个不错,学习以下
  回复  引用    
2007-06-28 22:27 | 放放[未注册用户]
楼主不错!!! 能否提供下载??谢谢!
  回复  引用    
2007-06-29 08:43 | Allen Zhang      
楼主,有个问题,如果更新中包含子文件夹呢? 要如何实现呀?
  回复  引用  查看    
#11楼[楼主]
2007-06-29 11:10 | 王学军      
@Allen Zhang
回“关于子文件夹”功能的实现:

思路比较简单 将Default.aspx返回的XML中加入所有文件的相对于Update目录的全路径如:
c:\intpubs\Update\f1.dll
c:\intpubs\Update\dic\f2.dll
则返回的XML应该为:
<xml><row File="f1.dll" date="..." /><row File="/dic/f2.dll" date=".."/></xml>
到客户端 解析文件名中有没有//包含的目录路径,有则创建,如需要实现N层子目录,则程序更复杂一点

程序代码如下:
1.修改Default.aspx 找到这段话:
DirectoryInfo di = new DirectoryInfo(UpdatePath);
foreach (FileInfo fi in di.GetFiles())
{
// 判断如果是Default.aspx则不下载到客户端
if (fi.Name == "Default.aspx") continue;
FileList.Add(fi.Name, fi.LastWriteTime.ToString());
}
修改为:
foreach (DirectoryInfo di in DirectoryInfo(UpdatePath).GetDirectories())
{
foreach (FileInfo fi in di.GetFiles())
{
// 这个地方我没调试 基本思路就是将每个文件的全路径改成相对于Update的路径,如fi.FullName返回c:\intpubs\Update\dic\f2.dll,将这个字符串中的"c:\intpubs\update"替换为空,就成了\dic\f2.dll了,然后再将\改为/ 以方便到客户端后http访问
FileList.Add(fi.FullName.Replace(UpdatePath, "").Replace("/", "\\"), "");
}
}

2. 在客户端解析这个XML时 对文件名进行解析 发现有目录路径就创建 也很简单 我就不多写了

  回复  引用  查看    
#12楼[楼主]
2007-06-29 11:12 | 王学军      
@放放
如果你需要 可以MAIL给你一份

  回复  引用  查看    
2007-06-29 14:57 | 放放[未注册用户]
谢谢楼主!!请发送Email 到 hzjianglf@163.com 谢谢
  回复  引用    
2007-06-29 15:08 | onekey      
根据时间来判断更新的文件不妥吧?
下载下来的文件的modifydate和源文件的不一样吧

  回复  引用  查看    
#15楼[楼主]
2007-06-29 15:10 | 王学军      
@onekey
请看我程序中的一句话:

// 将下载回来的文件修改时间更正 WebClient不会做这事只有我们来做了
FileInfo f = new FileInfo(newFilePath);
f.LastWriteTime = currentResource.LastModified;

与源文件不一样我们就改成一样的噻

  回复  引用  查看    
2007-06-29 15:52 | 放放[未注册用户]
谢谢,已经收到!
我原先用的自动更新用的是delphi写的,是不判断文件时间的,因为文件的时间在客户端可以自己改的,我们ERP一些升级是必须升级的,因此不管任何情况,只要点了自动更新,下载服务器文件列表,不管版本新不新,都会重新下载一遍,从而保证了程序的最新!

请问有没办法为了减少下载量,先下载压缩包,然后在客户端解压更新??

  回复  引用    
#17楼[楼主]
2007-06-29 16:00 | 王学军      
@放放
首先文件的Modify时间是没办法很容易的修改的 不必要担心这个

如果你的ERP的数据库是远程模式 就不能使用主动更新必须是强制更新
方式 每次登录时必须检查更新 这种情况不建议使用全部下载

压缩这个曾经做过 很多解压软件都有命令行方式 使用这个技巧喽

  回复  引用  查看    
2007-07-04 13:32 | ColdDog      
@王学军
你提到"服务器有一虚拟目录Update,里面放置客户端的所有程序"——这么说,客户端程序必须放在IIS控制下的某个目录里了?

  回复  引用  查看    
2007-07-04 13:34 | ColdDog      
如果可以,也发一份Demo代码给我参考一下吧,谢了~
linxinxuan (at) hotmail.com

  回复  引用  查看    
#20楼[楼主]
2007-07-05 15:39 | 王学军      
@ColdDog

如果想不放到Update目录里 那就自己写个ASP程序来读取文件流 再返回给客户

  回复  引用  查看    
2007-07-20 16:47 | zhongfeng[未注册用户]
主程序文件已经打开,不能覆盖吧?
  回复  引用    
#22楼[楼主]
2007-07-20 16:50 | 王学军      
嗯 是的 你看我程序是下载到Update目录中暂存的

后面的做法我忘记说了 我编写了一个AppCopy.exe的程序 专门负责将下载完的所有文件覆盖过去 下载完了以后就调用这个程序 然后关闭自己

  回复  引用  查看    
2008-02-18 14:32 | sjean[未注册用户]
学习中,能否也发份给我学习下~shenfengxian@163.com
  回复  引用    
2008-03-28 22:36 | 林晓伟[未注册用户]
可以发份源码例子吗,新手上路走不稳,帮忙一下啰,我的邮箱tatarlxw@163.com
  回复  引用    
2008-03-29 10:44 | 官欣[未注册用户]
楼主,我也是新手,发一份完整的演示例子给我吧,谢谢!邮箱:guanxinye@163.com
  回复  引用    
2008-04-12 11:38 | feiyvefanli[未注册用户]
楼主 我急需做这样一个自动更新功能的程序 找到这里来了
能不能发给我一份代码例子给我 大恩大德不胜感激
我的邮箱是feiyvefanli@gmail.com

  回复  引用    
2008-07-20 17:44 | 饥饿的狼      
HI,楼主你好!麻烦发一分实例给我,谢谢!
Longsea168@163.com

  回复  引用  查看    
2008-08-07 16:41 | Master_t[未注册用户]
你的例子我看了,基本通过了,但是在下载文件的时候出了些问题。请你发一份Demo代码给我吧! 谢谢 对了如果有好的加密算法也顺道发一下吧!最近在搞这方面的东西,数学不好很头痛啊!
Master_t@163.com

  回复  引用    
2008-12-04 10:42 | stoneca[未注册用户]
写的真不错! 请发一份给我了: shi5588 $ gmail.com 谢谢!
  回复  引用    
2009-01-05 14:58 | 射手鼠[未注册用户]
写的不错送给俺一份吧dongchaozhou@sina.com或zdc2000@msn.com
  回复  引用    
2009-04-15 10:08 | 呆呆盼盼[未注册用户]
最近在剛好需要使用ClickOnce功能, 可是有些想做的ClickOnce又不提供, 研究樓主提供的程式碼, 可能資質不夠, 不是很懂!!
能否寄一份可執行的程式碼, 讓我參考呢? 謝謝! ^_^

mail: shenbb@pchome.com.tw

  回复  引用    
2009-04-27 17:49 | 兵哥[未注册用户]
伟大的LZ,发一份demo给我吧,我急需要~
e-mail:huyibing1985@163.com

  回复  引用    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 799147




相关文章:

相关链接: