Winform uses FTP to implement automatic updates

Background of the project

The company developed a Winform program and installed it on dozens of devices. Manual updating was very troublesome. In order to facilitate program updating, we developed an automatic update plug-in based on ftp. You can set the version, start the program, and update the time. The following is the rendering.

 Project use

Put the Update.exe and UpdateConfig.xml files into your program directory and write the xml configuration file according to the needs of your project

<?xml version="1.0" encoding="utf-8" ?>
<configs>
  <config name="version" value="1.02" />
  <config name="ftpUrl" value="127.0.0.1/Update/DyePlanQuery/Current" />
  <config name="exclude" value="WinFormsApp.exe.config,WinFormsApp.vshost.exe.config" />
  <config name="runApp" value="WinFormsApp.exe" />
  <config name="clearApp" value="WinFormsApp.exe" />
</configs>

vesrion is the version number, ftpUrl is the path of your server FTP, I built the FTP based on IIS

 The server also has UpdateConfig.xml, and the program will update the program based on the xml comparison between the client and the server.

 After runApp is configured, the application can be automatically started after the update. The configuration is the name of the application exe.

Project introduction

The project contains three main implementation classes: ConfigHelper, FTPClient, and Tools

MainForm is responsible for obtaining the download list, batch downloading, and displaying the download progress bar.

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private void MainForm_Shown(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(ConfigHelper.config.ftpUrl))
        {
            MessageBox.Show("参数初始化失败!");
            this.Close();
            return;
        }
        FTPClient fTPClient = new FTPClient(ConfigHelper.config.ftpUrl);

        if (!fTPClient.IsNeedUpdate())
        {
            MessageBoxButtons messButton = MessageBoxButtons.OKCancel;
            DialogResult dr = MessageBox.Show("没有检测到新版本,是否继续更新?", "信息", messButton);

            if (dr == DialogResult.OK)//如果点击“确定”按钮
            {

            }
            else//如果点击“取消”按钮
            {
                this.Close();
                return;
            }
        }

        var fileList = fTPClient.GetFileList();

        int totalCount = fileList.Count();
        for (var i = 0; i < totalCount; i++)
        {
            string fileInfo = fileList[i];
            string[] fileInfoArry = fileInfo.Split(' ');
            string fileName = fileInfoArry[fileInfoArry.Count() - 1];

            //如果是文件夹
            if (fileInfo.Contains("<DIR>"))
            {

            }
            else
            {
                if (!fileName.Contains("Update.exe"))
                {
                    var PrintingProduction = fTPClient.Download(fileName);
                    if (!PrintingProduction)
                    {
                        MessageBox.Show(fileName + "文件未更新请重新下载!");
                        return;
                    }
                }
            }
            System.Threading.Thread.Sleep(100);
            progressBar1.Value = i * 100 / totalCount;
        }
        progressBar1.Value = 100;
        //MessageBox.Show("更新成功!");

        Tools.RunApp();

        this.Close();
        Application.Exit();
    }

   
}

The main function of ConfigHelper is to read XML configuration files

 

public static class ConfigHelper
{
    public static Config config = null;
  
    //初次加载本地配置文件
    public static void Load()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("UpdateConfig.xml");

        config = ReadXml(doc);
    }
    //读取xml
    public static Config ReadXml(XmlDocument doc)
    {
        Config read_config = new Config();
        var nodeList = doc.SelectNodes(@"configs/config");

        foreach (XmlNode items in nodeList)
        {
            if (items.Attributes["name"].Value == "version")
            {
                read_config.version = items.Attributes["value"].Value;
            }
            else if (items.Attributes["name"].Value == "ftpUrl")
            {
                read_config.ftpUrl = items.Attributes["value"].Value;
            }
            else if (items.Attributes["name"].Value == "runApp")
            {
                read_config.runApp = items.Attributes["value"].Value;
            }
            else if (items.Attributes["name"].Value == "exclude")
            {
                if (!string.IsNullOrEmpty(items.Attributes["value"].Value))
                {
                    read_config.exclude = items.Attributes["value"].Value.Split(',').ToList();
                }
            }
            else if (items.Attributes["name"].Value == "clearApp")
            {
                if (!string.IsNullOrEmpty(items.Attributes["value"].Value))
                {
                    read_config.clearApp = items.Attributes["value"].Value.Split(',').ToList();
                }
            }
        }
        return read_config;
    }
}

The main function of FTPClient is to communicate with the server FTP

 

public class FTPClient
{
    //ftp路径
    public string ftpUrl = "";
    //构造函数
    public FTPClient(string ftpUrl)
    {
        this.ftpUrl = "ftp://" + ftpUrl;
    }
    //获取文件列表
    public string[] GetFileList()
    {
        try
        {
            StringBuilder result = new StringBuilder();//如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类。
            FtpWebRequest ftp;
            ftp = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpUrl));
            //ftp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);
            ftp.Method = WebRequestMethods.Ftp.ListDirectoryDetails;//目录
            WebResponse response = ftp.GetResponse();
            StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.Default);//读入responses所创建的数据流
            string line = reader.ReadLine();//输入流中的下一行;如果到达了输入流的末尾,则为空引用
            while (line != null)
            {
                result.Append(line);//)Append 方法可用来将文本或对象的字符串表示形式添加到由当前 StringBuilder 对象表示的字符串的结尾处。
                result.Append("\n");
                line = reader.ReadLine();
            }
            result.Remove(result.ToString().LastIndexOf("\n"), 1);//移除最后的换行》?
            reader.Close();
            response.Close();
            return result.ToString().Split('\n');
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    //下载
    public bool Download(string fileUrl)
    {
        //是否排除
        if (ConfigHelper.config.exclude.Contains(fileUrl))
        {
            return true;
        }
        FtpWebRequest reqFtp = null;
        FtpWebResponse response = null;
        Stream ftpStream = null;
        FileStream outputStream = null;
        try
        {
            string newFileName = System.IO.Directory.GetCurrentDirectory() + "/" + fileUrl;//给原名字
            if (File.Exists(newFileName))
            {
                try
                {
                    File.Delete(newFileName);//如果已有则删除已有
                }
                catch { }

            }

            string url = ftpUrl + "/" + fileUrl;
            //URL是URI的子集,统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。统一资源定位符URL就是用定位的方式实现的URI。
            //URI可被视为定位符(URL),名称(URN)或两者兼备。统一资源名(URN)如同一个人的名称,而统一资源定位符(URL)代表一个人的住址。
            //换言之,URN定义某事物的身份,而URL提供查找该事物的方法。
            reqFtp = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));//创造连接,创建一个FtpWebRequest对象,指向ftp服务器的uri
            reqFtp.UseBinary = true;//二进制传输,给FtpWebRequest对象设置属性
            reqFtp.Method = WebRequestMethods.Ftp.DownloadFile;// 设置ftp的执行方法(上传,下载等)

            //reqFtp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);//账户密码
            response = (FtpWebResponse)reqFtp.GetResponse();//获得文件,执行请求
            ftpStream = response.GetResponseStream();//获得流,接收相应流
            long cl = GetFileSize(url);//获得文件大小cl
            int bufferSize = 2048;//时间延迟
            int readCount;
            byte[] buffer = new byte[bufferSize];
            readCount = ftpStream.Read(buffer, 0, bufferSize);//从流里读取的字节数为0时,则下载结束。
            outputStream = new FileStream(newFileName, FileMode.Create);//输出流

            while (readCount > 0)
            {
                outputStream.Write(buffer, 0, readCount);//写入
                readCount = ftpStream.Read(buffer, 0, bufferSize);//写入
            }
            return true;
        }
        catch (Exception ex)//捕捉
        {
            throw;
        }
        finally//异常清理,无论是否异常,都会执行
        {
            if (reqFtp != null)
            {
                reqFtp.Abort();
            }
            if (response != null)
            {
                response.Close();
            }
            if (ftpStream != null)
            {
                ftpStream.Close();
            }
            if (outputStream != null)
            {
                outputStream.Close();
            }
        }
    }
    //获取文件大小
    public static long GetFileSize(string url)
    {
        long fileSize = 0;
        try
        {
            FtpWebRequest reqFtp = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));//路径创造新的连接
            reqFtp.UseBinary = true;
            //reqFtp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);//连接
            reqFtp.Method = WebRequestMethods.Ftp.GetFileSize;//方法大小
            FtpWebResponse response = (FtpWebResponse)reqFtp.GetResponse();
            fileSize = response.ContentLength;//获得大小

            response.Close();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
        return fileSize;
    }
    //获取服务器配置文件
    public Config GetServerConfig()
    {
        Config serverConfig = null;
        string fileUrl = "UpdateConfig.xml";
        FtpWebRequest reqFtp = null;
        FtpWebResponse response = null;
        Stream ftpStream = null;
        FileStream outputStream = null;
        try
        {
            string url = ftpUrl + "/" + fileUrl;
            //URL是URI的子集,统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。统一资源定位符URL就是用定位的方式实现的URI。
            //URI可被视为定位符(URL),名称(URN)或两者兼备。统一资源名(URN)如同一个人的名称,而统一资源定位符(URL)代表一个人的住址。
            //换言之,URN定义某事物的身份,而URL提供查找该事物的方法。
            reqFtp = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));//创造连接,创建一个FtpWebRequest对象,指向ftp服务器的uri
            reqFtp.UseBinary = true;//二进制传输,给FtpWebRequest对象设置属性
            reqFtp.Method = WebRequestMethods.Ftp.DownloadFile;// 设置ftp的执行方法(上传,下载等)

            //reqFtp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);//账户密码
            response = (FtpWebResponse)reqFtp.GetResponse();//获得文件,执行请求
            ftpStream = response.GetResponseStream();//获得流,接收相应流

            //读取XML文件
            XmlDocument doc = new XmlDocument();
            doc.Load(ftpStream);

            serverConfig = ConfigHelper.ReadXml(doc);

        }
        catch (Exception ex)//捕捉
        {
            throw;
        }
        finally//异常清理,无论是否异常,都会执行
        {
            if (reqFtp != null)
            {
                reqFtp.Abort();
            }
            if (response != null)
            {
                response.Close();
            }
            if (ftpStream != null)
            {
                ftpStream.Close();
            }
            if (outputStream != null)
            {
                outputStream.Close();
            }
        }
        return serverConfig;
    }
    //判断是否需要更新
    public bool IsNeedUpdate()
    {
        bool _bool = true;
        Config serverConfig = GetServerConfig();
        if (serverConfig.version == ConfigHelper.config.version)
        {
            _bool = false;
        }
        return _bool;
    }
   
}

Tools contains some public methods

public class Tools
{
    static void RunAppThread()
    {
        string url = System.IO.Directory.GetCurrentDirectory() + "\\" + ConfigHelper.config.runApp;
        Process.Start(url);
    }
    //启动APP
    public static void RunApp()
    {
        if (!string.IsNullOrEmpty(ConfigHelper.config.runApp))
        {
            System.Threading.Thread oThread = new System.Threading.Thread(new System.Threading.ThreadStart(RunAppThread));
            oThread.Start();
        }
    }
    //删除APP
    public static void ClearRunApp()
    {
        if (ConfigHelper.config.clearApp.Count > 0)
        {
            foreach (var items in ConfigHelper.config.clearApp)
            {
                System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(items);
                foreach (System.Diagnostics.Process process in processes)
                {
                    process.Kill();
                }
            }
        }
    }
}

 used in projects

First introduce update.exe into the project

using BrickDogUpdate = Update;

Add a timer to the project and set the time point you want to update. If the server is updated, the current program will be closed and the update program will be started. After the update is completed, the program will be started again.

private void timer1_Tick(object sender, EventArgs e)
{
    //更新 23:59:59 11:59:59
    if (DateTime.Now.ToString("HH:mm:ss") == "23:59:59" || DateTime.Now.ToString("HH:mm:ss") == "11:59:59")
    {
        //加载配置文件
        BrickDogUpdate.ConfigHelper.Load();
        BrickDogUpdate.FTPClient fTPClient = new Update.FTPClient(BrickDogUpdate.ConfigHelper.config.ftpUrl);
        //与FTP服务器比较版本号是否需要更新
        if (fTPClient.IsNeedUpdate())
        {
            Start_Update_Thread();
            Application.Exit();
        }
    }
}

void Start_Update_Thread()
{
    System.Threading.Thread oThread = new System.Threading.Thread(new System.Threading.ThreadStart(Update_Thread));
    oThread.Start();
}

void Update_Thread()
{
    string updateUrl = System.IO.Directory.GetCurrentDirectory() + "\\Update.exe";
    Process.Start(updateUrl);
}

other

The .vshost process will cause the process to be shut down incompletely. We configure it as follows.

On the debugging page, change it to release and cancel the last item to enable the hosting process.

 On the generation page, change the debugging information in the advanced options to none to cancel the generation of pdb files.

Guess you like

Origin blog.csdn.net/qq243348167/article/details/126623611