C#实现软件自动升级

 

winform程序相对web程序而言,功能更强大,编程更方便,但软件更新却至关麻烦,要到客户端一台一台地升级,本文结合实际状况,经过软件实现自动升级,弥补了这一缺陷,有较好的参考价值。
因为程序在运行时不能用新的版本覆盖本身,所以,咱们将登陆窗口单独作成一个可执行文件,用户登陆时,从网上检测是否有新的主程序,若是有,则从后台下载并覆盖老的版本,用户输入正确的用户名和密码后,经过参数将必要的信息(如用户名、密码等)传递给主程序,实现登陆,咱们仍是以实际例子来讲明。

建立一个项目,不妨取名为MainPro,做为主程序,切换到代码窗口,看到以下一段代码:
         ///
         /// 应用程序的主入口点。
         ///
         [STAThread]
         static void Main()
         {
              Application.Run(new Form1());
      }
web

为了接收参数,咱们添加两个静态变量m_UserName和m_Password用于存放用户名和密码,并修改Main函数为:

         private static string m_UserName,m_Password;
         ///
         /// 应用程序的主入口点。
         ///
         [STAThread]
         static void Main(string[] args)
         {
              if(args.Length==2)//有参数输入,你还能够根据实际状况传入更多参数
              {
                  //记录下用户名和密码,供软件使用
                   m_UserName=args[0];
                   m_Password=args[1];
                   Application.Run(new Form1());
              }
              else
              {
                   MessageBox.Show("不能从这里启动");
              }
      }
数据库

为了显示登陆是否正确,Load事件的代码修改成:

         private void Form1_Load(object sender, System.EventArgs e)
         {
              string msg=string.Format("用户名:{0},密码:{1}",m_UserName,m_Password);
              MessageBox.Show(msg);
编程

      }
这样,咱们的示例主程序就完成了,只有加入参数才能运行该主程序,例如咱们在DOS窗口中用"mainpro user pass"来启动该软件。
因为本项目涉及到不止一个程序,为保证运行正确,须要将编译后的可执行文件放到同一个文件夹,尽管咱们能够编译后再将文件复制到同一个文件夹中,但每次都手工复制比较费事,这里采起一个简单的办法。先在硬盘中建立一个文件夹,如D:\output,选择菜单"项目"→"属性",会弹出一个对话框,在配置(C)后面选择"全部配置",选择配置属性的生成项,在输出路径中输入"D:\output"(以下图),再编译时你就发现,输出的可执行文件乖乖地跑到D:\output下面了。
 
接下来作一个上传工具,目的是将最新版本上传到服务器上,为简单,咱们这里使用access数据库,固然,在网络版中可使用SQL Server,原理彻底同样。
在D:\output下新建一个access数据库,取名为mydatabase.mdb吧,新建两个表,一个为操做员,用来存放操做员的姓名和密码,另一个为版本,用来存放主程序的最新版本,两个表的结构以下:

操做员表
版本表
字段名
类型
用途
字段名
类型
用途
序号
长整型
主键
序号
长整型
主键
姓名
字符
用户名
版本号
长整型
存放当前版本
文件名称
字符
本记录对应的文件名
密码
字符
密码
文件内容
OLE 对象,SQL 中为Image
存放文件的具体内容

咱们手工输入一些用户名和密码,以下:
 
不要关闭刚才的主程序,直接选择菜单"文件"→"添加项目"→"新建项目",输入项目名称为"UpLoad",以下图:
 
点"肯定",一样,配置输出路径为D:\output。
在窗口上放入三个按钮(浏览(btnBrow)、肯定(btnOK)和取消(btnCancel))、两个文本框(txtFileName,txtVersion)和相应的文字说明,以下图:
 
在"解决方案资源管理器"窗口中,选择"upload"项目,单击鼠标右键,选择"设为启动项目",就能够运行该程序了。
添加浏览按钮的响应代码以下:

         private void btnBrow_Click(object sender, System.EventArgs e)
         {
              OpenFileDialog myForm=new OpenFileDialog();
              myForm.Filter="应用程序(*.exe)|*.exe|全部程序(*.*)|*.*";
              if(myForm.ShowDialog()==DialogResult.OK)
              {
                   this.txtFileName.Text=myForm.FileName;
              }
      }
服务器

该按钮的做用是获得要上传文件的文件名称(实际应用中,还能够根据获得的文件名,从数据库中获得相对应文件的最高版本号,自动填入的版本号文本框中供输入新版本号时参考)。
添加取消按钮响应代码,目的是关闭窗口:

         private void btnCancel_Click(object sender, System.EventArgs e)
         {
              this.Close();
      }
网络

添加两个引用:

         using System.Data.OleDb;
         using System.IO;
ide

再添加两个变量:

         private DataSet m_DataSet=new DataSet();
         private string m_TableName="版本";
函数

下面的函数去掉文件名中的路径:

         ///
         /// 从一个含有路径的文件名中分离出文件名
         ///
         /// 包含路径的文件名
         /// 去掉路径的文件名
         private string GetFileNameFromPath(string p_Path)
         {
              string strResult="";
              int nStart=p_Path.LastIndexOf("\\");
              if(nStart>0)
              {
                   strResult=p_Path.Substring(nStart+1,p_Path.Length-nStart-1);
              }
              return strResult;
      }
工具

添加肯定按钮响应代码(含注释):

private void btnOK_Click(object sender, System.EventArgs e)
         {
              //检查版本号是否合法
              try
              {
                   Decimal.Parse(this.txtVersion.Text);
              }
              catch
              {
                   MessageBox.Show("无效的版本号!");
                   this.txtVersion.Focus();
                   this.txtVersion.SelectAll();
                   return;
              }
 
              if(this.txtFileName.Text.Trim().Length>0)
              {
                   //检查文件是否存在
                   if(!File.Exists(this.txtFileName.Text.Trim()))
                   {
                       MessageBox.Show("文件不存在!");
                       return;
                   }
 
                   //链接数据库
                   string strConnection="Provider = Microsoft.Jet.OLEDB.4.0 ;Jet OLEDB:Database Password=;Data Source ="+
                                          Application.StartupPath.ToString().Trim()+"\\mydatabase.mdb" ;
                   OleDbConnection myConnect=new OleDbConnection(strConnection);
                   OleDbCommand myCommand=new OleDbCommand("select * from 版本",myConnect);
                   OleDbDataAdapter myDataAdapter=new OleDbDataAdapter();
                   myDataAdapter.SelectCommand=myCommand;
                   OleDbCommandBuilder myCommandBuilder=new OleDbCommandBuilder(myDataAdapter);
                   myConnect.Open();
 
                   //获取已有的数据
                   m_DataSet=new DataSet();
                   try
                   {
                       myDataAdapter.Fill(m_DataSet,this.m_TableName);
                       //若是是首次上传,则增长一条记录
                       if(m_DataSet.Tables[m_TableName].Rows.Count==0)
                       {
                            DataRow newrow=m_DataSet.Tables[m_TableName].NewRow();
                            newrow["序号"]="1";
                            m_DataSet.Tables[m_TableName].Rows.Add(newrow);
                       }
                      
                       DataRow row=m_DataSet.Tables[m_TableName].Rows[0];
                       //填入去掉路径的文件名称
                       row["文件名称"]=this.GetFileNameFromPath(this.txtFileName.Text.Trim());
                       //填入版本号
                       row["版本号"]=this.txtVersion.Text.Trim();
 
                       //将实际文件存入记录中
                       FileStream fs=new FileStream(this.txtFileName.Text.Trim(),FileMode.Open);
                       byte [] myData = new Byte [fs.Length ];
                       fs.Position = 0;
                       fs.Read (myData,0,Convert.ToInt32 (fs.Length ));
                       row["文件内容"] = myData;
                       fs.Close();//关闭文件
 
                       //更新数据库
                       myDataAdapter.Update(this.m_DataSet,this.m_TableName);
                        myConnect.Close();
                       MessageBox.Show("文件更新成功!");
                   }
                   catch(Exception ee)
                   {
                       MessageBox.Show(ee.Message);
                   }
                  
              }
              else
              {
                   MessageBox.Show("请输入文件名");
              }
      }
ui

至此,上传工具制做完成,经过该程序,能够上传主程序文件,固然,该工具是给软件开发供应商用于发布新软件用的,千万不要给用户哦。
最后是编写登陆程序,按照编写上传工具的方法添加一个项目,项目名称为Login,设置输出路径为D:\Output,并设置该项目为启动项目。
添加一个组合框(combUserName),设置DropDownStyle为DropDownList,用来选择已有的用户名,添加一个用于输入密码的文本框(txtPassword),设置PasswordChar属性为"*",并在前面加入相应的文字标签,再添加肯定(btnOK)和取消(btnCancel)按钮,并将肯定按钮的Enable属性设置为false,目的是若是新软件没有下载完成,不许登陆,布置以下图:
 
切换到代码窗口,添加引用:

using System.Data.OleDb;
using System.Threading;
using System.IO;
using Microsoft.Win32;
 
this

再添加以下变量:

         ///
         /// 存放操做员及密码的DataSet
         ///
         private DataSet m_DataSet;
         ///
         /// 本功能用到的数据库表
         ///
         private string m_TableName="操做员";
         private DataTable m_Table;

为了不每次都下载主程序,咱们将当前主程序的版本号要保存下来,我采用的办法是保存到注册表中,为此,写两个函数,用于读取/写入注册表,以下:

         ///
         /// 定义本软件在注册表中software下的公司名和软件名称
         ///
         private string m_companyname="lqjt",m_softwarename="autologin";
         ///
         /// 从注册表中读信息;
         ///
         /// 要读取的键值
         /// 读到的键值字符串,若是失败(如注册表尚无信息),则返回""
         private string ReadInfo(string p_KeyName)
         {
              RegistryKey SoftwareKey=Registry.LocalMachine.OpenSubKey("Software",true);
              RegistryKey CompanyKey=SoftwareKey.OpenSubKey(m_companyname);
              string strValue="";
             
              if(CompanyKey==null)
                   return "";
              RegistryKey SoftwareNameKey=CompanyKey.OpenSubKey(m_softwarename);//创建
              if(SoftwareNameKey==null)
                   return "";
 
              try
              {
                   strValue=SoftwareNameKey.GetValue(p_KeyName).ToString().Trim();
              }
              catch
              {}
 
              if(strValue==null)
                   strValue="";
              return strValue;
         }
         ///
         /// 将信息写入注册表
         ///
         /// 键名
         /// 键值
         private void WriteInfo(string p_keyname,string p_keyvalue)
         {
              RegistryKey SoftwareKey=Registry.LocalMachine.OpenSubKey("Software",true);
              RegistryKey CompanyKey=SoftwareKey.CreateSubKey(m_companyname);
              RegistryKey SoftwareNameKey=CompanyKey.CreateSubKey(m_softwarename);
              //写入相应信息
              SoftwareNameKey.SetValue(p_keyname,p_keyvalue);
      }

再写一个函数,用户来获取用户名/密码和更新主程序版本:

///
         /// 获取操做员状况,同时更新主程序版本
         ///
         private void GetInfo()
         {
              this.m_DataSet=new DataSet();
              this.combUsers.Items.Clear();
              string strSql=string.Format("SELECT * FROM  操做员 ORDER BY 姓名");
 
              //链接数据库
              string strConnection="Provider = Microsoft.Jet.OLEDB.4.0 ;Jet OLEDB:Database Password=;Data Source ="+
                   Application.StartupPath.ToString().Trim()+"\\mydatabase.mdb" ;
              OleDbConnection myConnect=new OleDbConnection(strConnection);
              OleDbCommand myCommand=new OleDbCommand(strSql,myConnect);
              OleDbDataAdapter myDataAdapter=new OleDbDataAdapter();
              myDataAdapter.SelectCommand=myCommand;
              try
              {
                   myConnect.Open();
 
                   //获取操做员信息
                   myDataAdapter.Fill(this.m_DataSet,this.m_TableName);
                   //将查询到的用户名填充到组合框供用户选择
                   this.m_Table=this.m_DataSet.Tables[this.m_TableName];
                   foreach(DataRow row in m_DataSet.Tables[m_TableName].Rows)
                   {
                       this.combUsers.Items.Add(row["姓名"]).ToString().Trim();
                   }
 
                   //检查是否有新的版本
                   DataSet dataset=new DataSet();
                   string tablename="tablename";
                   //为减小数据传送时间,不获取文件内容
                   strSql="select 文件名称,版本号 from 版本";
                   myCommand=new OleDbCommand(strSql,myConnect);
                   myDataAdapter=new OleDbDataAdapter();
                   myDataAdapter.SelectCommand=myCommand;
                   myDataAdapter.Fill(dataset,tablename);
                   if(dataset.Tables[tablename].Rows.Count==1)//有文件
                   {
                       string filename=dataset.Tables[tablename].Rows[0]["文件名称"].ToString();
                       string version=dataset.Tables[tablename].Rows[0]["版本号"].ToString();
                       //读入本机主程序的版本号
                       string oldversion=this.ReadInfo(filename);
                       if(oldversion.Length==0)//不存在
                            oldversion="0";
                       if(Decimal.Parse(version)>Decimal.Parse(oldversion))//有新的版本出现
                       {
                            //取回文件内容
                            dataset=new DataSet();
                            strSql="select * from 版本";
                            myCommand=new OleDbCommand(strSql,myConnect);
                            myDataAdapter=new OleDbDataAdapter();
                            myDataAdapter.SelectCommand=myCommand;
                            myDataAdapter.Fill(dataset,tablename);
                            //将文件下载到本地
                            DataRow row=dataset.Tables[tablename].Rows[0];
                            if(row["文件内容"]!=DBNull.Value)
                            {
 
                                 Byte[] byteBLOBData =  new Byte[0];
                                 byteBLOBData = (Byte[])row["文件内容"];
                                 try
                                 {
                                     FileStream fs=new FileStream(Application.StartupPath+"\\"+filename,FileMode.OpenOrCreate);
                                     fs.Write(byteBLOBData,0,byteBLOBData.Length);
                                     fs.Close();
                                     //写入当前版本号,供下次使用
                                     this.WriteInfo(filename,version);
                                 }
                                 catch(Exception ee)
                                 {
                                     MessageBox.Show(ee.Message);
                                 }
                            }
 
                       }//有新版本
                   }//有文件
 
                   //关闭链接
                   myConnect.Close();
              }
              catch(Exception ee)
              {
                   MessageBox.Show(ee.Message);
                   return;
              }
              //容许登陆
              this.btnOK.Enabled=true;

      }
为了避免让用户等待过久,在启动时经过一个线程,让获取用户信息和更新在后台完成,即在窗口Load事件中,经过线程调用上面的GetInfo的函数,故窗口Load代码以下:

         private void Form1_Load(object sender, System.EventArgs e)
         {
              //为加快显示速度,将数据库链接等放到另一个线程中去
              Thread thread=new Thread(new ThreadStart(GetInfo));
              thread.Start();
      }

有了上述准备,咱们来编写肯定按钮的响应代码以下:

private void btnOK_Click(object sender, System.EventArgs e)
         {
              //根据组合框的选择,获得当前用户在DataSet中具体物理位置
              if(this.combUsers.SelectedIndex<0)//没有选择
                   return;
              DataRow rowNow=null;
              foreach(DataRow row in this.m_DataSet.Tables[this.m_TableName].Rows)
              {
                   if(row["姓名"].ToString().Trim()==this.combUsers.Text.Trim())
                   {
                       rowNow=row;
                       break;
                   }
              }
              if(rowNow==null)
                   return;
 
              //获取当前正确密码
              string strPassword=rowNow["密码"].ToString().Trim();
              this.txtPassword.Text=this.txtPassword.Text.Trim();
              if(this.txtPassword.Text==strPassword)//密码正确
              {
 
                   //主程序名称
                   string filename=Application.StartupPath+"\\"+"MainPro.exe";
                   //参数名称
                   string arg=this.combUsers.Text+" "+this.txtPassword.Text;
                   //运行主程序
                   System.Diagnostics.Process fun=System.Diagnostics.Process.Start(filename,arg);
 
                   //关闭登陆框
                   this.Close();
              }
              else
              {
                   MessageBox.Show("    密码错误!若是你确信密码输入正确,\n能够试着检查一下大写字母键是否按下去了。",
                       "警告",MessageBoxButtons.OK,MessageBoxIcon.Warning);
                   this.txtPassword.Focus();
                   this.txtPassword.SelectAll();
              }
      }

取消按钮的代码很是简单,就是关闭登陆窗口:

         private void btnCancel_Click(object sender, System.EventArgs e)
         {
              this.Close();

      }
把Login和MainPro软件连同其余相关文件打包成安装程序,将Login以快捷方式放到桌面或开始菜单中供用户使用(固然,快捷方式名称能够随便取了),用户运行Login后,会自动更新软件。
本例中全部代码请到 ftp://qydn.vicp.net/ 下载,文件名为update.rar,解压缩后别忘了在D:\建立一个output文件夹,并将mydatabase.mdb复制到该文件夹中。
说明:本文只起抛砖引玉的做用,经过该思路进行扩展能够完成许多功能,如经过修改上传/登陆程序,不只能够实现对主程序的更新,并且能够实现对任何要用到的资源文件进行更新,本例中不能实现对登陆框自己的更新,我采用的办法是在主程序的Closing事件中更新登陆窗口,由于此时登陆窗口已经关闭了。在登陆窗口中,能够放一个"保存密码"的复选框,若是用户选中该组合框,能够将用户名和密码保存到注册表中,下次登陆时直接读入,用户只要点肯定按钮便可,免去了每次都选用户名和输密码的烦恼,
在本例中,咱们能够看到,数据库的链接、查询等工做是重复性劳动,且三个个项目中用到的数据库、公司名称等是同样的,在实际工做中,咱们能够单独新建一个cs文件,不妨取名为MyTools.cs,将一些经常使用函数和变量(如数据库链接、公司名称等)作成静态的,各具体项目中连接本文件,而后直接使用,咱们只需修改MyTools.cs中的相关变量或函数而没必要在每一个项目中都去改,既方便又不会遗漏,MyTools.cs参考以下:

///
///预编译选项,若是定义了NETWORKVERSION,,表示是网络版,使用SQL2000数据库,不然,使用ACCESS2000数据库
///
 
//#define NETWORKVERSION
 
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;
using System.Data;
 
#if NETWORKVERSION
using System.Data.SqlClient;
#else
using System.Data.OleDb;
#endif
using System.Reflection;
using Microsoft.Win32;
 
namespace OA
{
     public class Tool
     {
         public Tool()
         {
         }
         ///
         /// 主程序的文件名
         ///
         public const string FileName="OA.exe";
      public const string g_TitleName="丽汽集团办公自动化系统";
      public static string g_UserName;
         public static void WriteInfo(string p_keyname,string p_keyvalue)
         {
             ......
      }
//其余相似代码略......
 
}
}

若是一个项目中要用到MyTools中的内容,能够按以下方式进行:
在"解决方案资源管理器"窗口中选择该项目,选择菜单"项目"→"添加现有项",此时弹出打开文件对话框,文件类型设为全部文件(*.*),找到MyTools.cs,不要直接点打开按钮,看到了打开按钮后面的"↓"了吗?单击它能够弹出一个菜单,选择"连接文件(L)",这样插入的文件只是一个连接,不会生成副本(以下图)。
 
使用时,添加MyTools的应用,再使用Tool类中的公共函数,如:
using OA;
private void myFun()
{
 string s=Tool.FileName;
}
若是单位名称变了,咱们只要修改MyTools.cs中的变量就能够了,没必要到每一个项目中都去修改。
咱们还注意了一个细节:

///
///预编译选项,若是定义了NETWORKVERSION,,表示是网络版,使用SQL2000数据库,不然,使用ACCESS2000数据库
///
 
//#define NETWORKVERSION

咱们知道,对于ACCESS或Sql server等,除了链接方式外,其他操做几乎彻底同样,所以,咱们定义了一个选项(如上面的注释),若是#define NETWORKVERSION,表示是网络版,使用Sql server数据库,不然(将#define NETWORKVERSION注释掉)就是单机版,使用ACCESS数据库,在MyTools中咱们将两种链接方式有区别的地方分别编写,就能够经过是否注释掉#define NETWORKVERSION这一行分别生成单机版和网络版软件,参考代码以下:

     ///
         /// 根据SQL语句返回一个查询结果,主要用于只要求返回一个字段的一个结果的状况
         ///
         /// 查询用到的SQL语句
         /// 查询到的结果,没有时则返回空""
         public static string GetAValue(string p_Sql)
         {
              string strResult="";
              Tool.OpenConn();
 
              //设计所须要返回的数据集的内容
              try
              {
                   // 打开指向数据库链接
#if NETWORKVERSION //网络版
                   SqlCommand aCommand = new SqlCommand ( p_Sql ,m_Connect ) ;
                   SqlDataReader aReader = aCommand.ExecuteReader ( ) ;
#else  //单机版,注意变量名aCommand和aReader在两个版本中都是同样的,有利于编程
                   OleDbCommand aCommand = new OleDbCommand ( p_Sql ,m_Connect ) ;
                   OleDbDataReader aReader = aCommand.ExecuteReader ( ) ;
#endif
                  
                   // 返回须要的数据集内容这里就不分单机版仍是网络版了,反正变量名同样
                   if(aReader.Read())
                       strResult=aReader[0].ToString();
                   aReader.Close () ;
 
              }
              catch(Exception ee)
              {
                   MessageBox.Show(ee.Message);
              }
              return strResult;
      }

以上相似的小技巧还不少,注意总结,定会收益多多。
相关文章
相关标签/搜索