一、引言
对于C/S架构来说,软件更新是一个很常用的功能,下面介绍一种非常实用的软件自动升级方案。
二、示意图
三、项目说明
3.1、项目创建
新建4个项目,如下所示:
3.2、项目关系
四、LinkTo.Toolkit
LinkTo.Toolkit主要是一些Utility及Helper类文件,实现转换扩展、文件读写、进程处理等功能。
/// <summary>
/// 转换扩展类
/// </summary>
public static class ConvertExtension
{
public static string ToString2(this object obj)
{
if (obj == null)
return ;
return obj.ToString();
}
public static DateTime? ToDateTime(this string str)
{
if (str)) return null;
if (str, out DateTime dateTime))
{
return dateTime;
}
return null;
}
public static bool ToBoolean(this string str)
{
if (str)) return false;
return () == bool.TrueS();
}
public static bool IsNullOrEmpty(this string str)
{
return (str);
}
public static int ToInt(this string str)
{
if (str, out int intValue))
{
return intValue;
}
return 0;
}
public static long ToLong(this string str)
{
if (str, out long longValue))
{
return longValue;
}
return 0;
}
public static decimal ToDecimal(this string str)
{
if (str, out decimal decimalValue))
{
return decimalValue;
}
return 0;
}
public static double ToDouble(this string str)
{
if (str, out double doubleValue))
{
return doubleValue;
}
return 0;
}
public static float ToFloat(this string str)
{
if (str, out float floatValue))
{
return floatValue;
}
return 0;
}
/// <summary>
/// DataRow转换为实体类
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dr"></param>
/// <returns></returns>
public static T ConvertToEntityByDataRow<T>(this DataRow dataRow) where T : new()
{
Type type = typeof(T);
PropertyInfo[] properties = ();
T t = new T();
if (dataRow == null) return t;
foreach (PropertyInfo property in properties)
{
foreach (DataColumn column in da)
{
if , S))
{
object value = dataRow[column];
if (value != null && value != DBNull.Value)
{
if ().Name != )
{
if )
{
(t, Enum.Parse, value.ToString()), null);
}
else
{
try
{
value = Convert.ChangeType(value, ) ?? ));
(t, value, null);
}
catch { }
}
}
else
{
(t, value, null);
}
}
else
{
(t, null, null);
}
break;
}
}
}
return t;
}
/// <summary>
/// 通用简单实体类型互转
/// </summary>
public static T ConvertToEntity<T>(this object sourceEntity) where T : new()
{
T t = new T();
Type sourceType = ();
if (typeof(DataRow)))
{
//DataRow类型
DataRow dataRow = sourceEntity as DataRow;
t = da;T>();
}
else
{
Type type = typeof(T);
PropertyInfo[] properties = ();
PropertyInfo[] sourceProperties = ();
foreach (PropertyInfo property in properties)
{
foreach (var sourceProperty in sourceProperties)
{
if , S))
{
object value = (sourceEntity, null);
if (value != null && value != DBNull.Value)
{
if != )
{
if )
{
(t, Enum.Parse, value.ToString()), null);
}
else
{
try
{
value = Convert.ChangeType(value, ) ?? ));
(t, value, null);
}
catch { }
}
}
else
{
(t, value, null);
}
}
else
{
(t, null, null);
}
break;
}
}
}
}
return t;
}
/// <summary>
/// 通用简单实体类型互转
/// </summary>
public static List<T> ConvertToEntityList<T>(this object list) where T : new()
{
List<T> t = new List<T>();
if (list == null) return t;
Type sourceObj = li();
if (typeof(DataTable)))
{
var dataTable = list as DataTable;
t = da;DataRow>().Where(m => ! == Da || m.RowState == Da)).Select(m => m.ConvertToEntityByDataRow<T>()).ToList();
}
else if (list is IEnumerable)
{
t = ((IList)list).Cast<object>().Select(m => m.ConvertToEntity<T>()).ToList();
}
return t;
}
/// <summary>
/// 转换为DataTable,如果是集合没有数据行的时候会抛异常。
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
public static DataTable ConvertToDataTable(this object list)
{
if (list == null) return null;
DataTable dataTable = new DataTable();
if (list is IEnumerable)
{
var li = (IList)list;
//li[0]代表的是一个对象,list没有行时,会抛异常。
PropertyInfo[] properties = li[0].GetType().GetProperties();
da(m => !m.Pro || !m.Pro).Select(m =>
new DataColumn, Nullable.GetUnderlyingType) ?? m.PropertyType)).ToArray());
foreach (var item in li)
{
DataRow dataRow = da();
foreach (PropertyInfo property in (m => m.Pro("Item") == null)) //过滤含有索引器的属性
{
object value = (item, null);
dataRow[] = value ?? DBNull.Value;
}
da(dataRow);
}
}
else
{
PropertyInfo[] properties = li().GetProperties();
properties = (m => m.Pro("Item") == null).ToArray(); //过滤含有索引器的属性
da(m => new DataColumn, Nullable.GetUnderlyingType) ?? m.PropertyType)).ToArray());
DataRow dataRow = da();
foreach (PropertyInfo property in properties)
{
object value = (list, null);
dataRow[] = value ?? DBNull.Value;
}
da(dataRow);
}
return dataTable;
}
/// <summary>
/// 实体类公共属性值复制
/// </summary>
/// <param name="entity"></param>
/// <param name="target"></param>
public static void CopyTo(this object entity, object target)
{
if (target == null) return;
if () != ())
return;
PropertyInfo[] properties = ().GetProperties();
foreach (PropertyInfo property in properties)
{
if .GetProperty("Item") != null)
continue;
object value = (entity, null);
if (value != null)
{
if (value is ICloneable)
{
(target, (value as ICloneable).Clone(), null);
}
else
{
(target, value.Copy(), null);
}
}
else
{
(target, null, null);
}
}
}
public static object Copy(this object obj)
{
if (obj == null) return null;
object targetDeepCopyObj;
Type targetType = obj.GetType();
if == true)
{
targetDeepCopyObj = obj;
}
else
{
targetDeepCopyObj = Ac(targetType); //创建引用对象
MemberInfo[] memberCollection = obj.GetType().GetMembers();
foreach (MemberInfo member in memberCollection)
{
if ().GetProperty("Item") != null)
continue;
if == MemberTy)
{
FieldInfo field = (FieldInfo)member;
object fieldValue = (obj);
if (fieldValue is ICloneable)
{
(targetDeepCopyObj, (fieldValue as ICloneable).Clone());
}
else
{
(targetDeepCopyObj, ());
}
}
else if == MemberTy)
{
PropertyInfo property = (PropertyInfo)member;
MethodInfo method = (false);
if (method != null)
{
object propertyValue = (obj, null);
if (propertyValue is ICloneable)
{
(targetDeepCopyObj, (propertyValue as ICloneable).Clone(), null);
}
else
{
(targetDeepCopyObj, (), null);
}
}
}
}
}
return targetDeepCopyObj;
}
}
public class FileHelper
{
private readonly string strUpdateFilesPath;
public FileHelper(string strDirector)
{
strUpdateFilesPath = strDirector;
}
//保存所有的文件信息
private List<FileInfo> listFiles = new List<FileInfo>();
public List<FileInfo> GetAllFilesInDirectory(string strDirector)
{
DirectoryInfo directory = new DirectoryInfo(strDirector);
DirectoryInfo[] directoryArray = direc();
FileInfo[] fileInfoArray = direc();
if > 0) li(fileInfoArray);
foreach (DirectoryInfo item in directoryArray)
{
DirectoryInfo directoryA = new DirectoryInfo);
DirectoryInfo[] directoryArrayA = direc();
GetAllFilesInDirectory);
}
return listFiles;
}
public string[] GetUpdateList(List<FileInfo> listFileInfo)
{
var fileArrary = li;FileInfo>().Select(s => s.FullName.Replace(strUpdateFilesPath, "")).ToArray();
return fileArrary;
}
/// <summary>
/// 删除文件夹下的所有文件但不删除目录
/// </summary>
/// <param name="dirRoot"></param>
public static void DeleteDirAllFile(string dirRoot)
{
DirectoryInfo directoryInfo = new DirectoryInfo(dirRoot));
FileInfo[] files = direc("*.*", Searc);
foreach (FileInfo item in files)
{
File.Delete);
}
}
}
public static class FileUtility
{
#region 读取文件
/// <summary>
/// 读取文件
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns></returns>
public static string ReadFile(string filePath)
{
string result = ;
if (filePath) == false)
{
return result;
}
try
{
using (var streamReader = new StreamReader(filePath, Encoding.UTF8))
{
result = ();
}
}
catch (Exception)
{
result = ;
}
return result;
}
#endregion 读文件
#region 写入文件
/// <summary>
/// 写入文件
/// </summary>
/// <param name="filePath">文件路径</param>
/// <param name="strValue">写入内容</param>
/// <returns></returns>
public static bool WriteFile(string filePath, string strValue)
{
try
{
if (filePath) == false)
{
using (FileStream fileStream = File.Create(filePath)) { }
}
using (var streamWriter = new StreamWriter(filePath, true, Encoding.UTF8))
{
(strValue);
}
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region 删除文件
/// <summary>
/// 删除文件
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns></returns>
public static bool DeleteFile(string filePath)
{
try
{
File.Delete(filePath);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion 删除文件
#region 为文件添加用户组的完全控制权限
/// <summary>
/// 为文件添加用户组的完全控制权限
/// </summary>
/// <param name="userGroup">用户组</param>
/// <param name="filePath">文件路径</param>
/// <returns></returns>
public static bool AddSecurityControll2File(string userGroup, string filePath)
{
try
{
//获取文件信息
FileInfo fileInfo = new FileInfo(filePath);
//获得该文件的访问权限
FileSecurity fileSecurity = ();
//添加用户组的访问权限规则--完全控制权限
(new FileSystemAccessRule(userGroup, FileSy, Acce));
//设置访问权限
(fileSecurity);
//返回结果
return true;
}
catch (Exception)
{
//返回结果
return false;
}
}
#endregion
#region 为文件夹添加用户组的完全控制权限
/// <summary>
/// 为文件夹添加用户组的完全控制权限
/// </summary>
/// <param name="userGroup">用户组</param>
/// <param name="dirPath">文件夹路径</param>
/// <returns></returns>
public static bool AddSecurityControll2Folder(string userGroup,string dirPath)
{
try
{
//获取文件夹信息
DirectoryInfo dir = new DirectoryInfo(dirPath);
//获得该文件夹的所有访问权限
DirectorySecurity dirSecurity = dir.GetAccessControl);
//设定文件ACL继承
InheritanceFlags inherits = In | In;
//添加用户组的访问权限规则--完全控制权限
FileSystemAccessRule usersFileSystemAccessRule = new FileSystemAccessRule(userGroup, FileSy, inherits, Pro, Acce);
dirSecuri, usersFileSystemAccessRule, out bool isModified);
//设置访问权限
dir.SetAccessControl(dirSecurity);
//返回结果
return true;
}
catch (Exception)
{
//返回结果
return false;
}
}
#endregion
}
public static class ProcessUtility
{
#region 关闭进程
/// <summary>
/// 关闭进程
/// </summary>
/// <param name="processName">进程名</param>
public static void KillProcess(string processName)
{
Process[] myproc = Proce();
foreach (Process item in myproc)
{
if == processName)
{
i();
}
}
}
#endregion
}
/// <summary>
/// Xml序列化与反序列化
/// </summary>
public static class XmlUtility
{
#region 序列化
/// <summary>
/// 序列化
/// </summary>
/// <param name="type">类型</param>
/// <param name="obj">对象</param>
/// <returns></returns>
public static string Serializer(Type type, object obj)
{
MemoryStream Stream = new MemoryStream();
XmlSerializer xml = new XmlSerializer(type);
try
{
//序列化对象
xml.Serialize(Stream, obj);
}
catch (InvalidOperationException)
{
throw;
}
S = 0;
StreamReader sr = new StreamReader(Stream);
string str = ();
();
S();
return str;
}
#endregion 序列化
#region 反序列化
/// <summary>
/// 反序列化
/// </summary>
/// <param name="type">类型</param>
/// <param name="xml">XML字符串</param>
/// <returns></returns>
public static object Deserialize(Type type, string xml)
{
try
{
using (StringReader sr = new StringReader(xml))
{
XmlSerializer xmldes = new XmlSerializer(type);
return xmldes.Deserialize(sr);
}
}
catch (Exception ex)
{
return ex.Message;
}
}
/// <summary>
/// 反序列化
/// </summary>
/// <param name="type"></param>
/// <param name="xml"></param>
/// <returns></returns>
public static object Deserialize(Type type, Stream stream)
{
XmlSerializer xmldes = new XmlSerializer(type);
return xmldes.Deserialize(stream);
}
#endregion 反序列化
}
五、AutoUpdaterTest
5.1、实体类
作用:本地配置Au文件的序列化及反序列化实体对象。
public class AutoUpdateConfig
{
/// <summary>
/// 自动升级模式:当前仅支持HTTP
/// </summary>
public string AutoUpdateMode { get; set; }
/// <summary>
/// HTTP自动升级模式时的URL地址
/// </summary>
public string AutoUpdateHttpUrl { get; set; }
}
5.2、通用类
作用:应用程序全局静态常量。全局参数都在此设置,方便统一管理。注:客户端是否检测更新,也是在此设置默认值。
/// <summary>
/// 应用程序全局静态常量
/// </summary>
public static class GlobalParam
{
#region 自动更新参数
/// <summary>
/// 是否检查自动更新:默认是true
/// </summary>
public static string CheckAutoUpdate = "true";
/// <summary>
/// 本地自动更新配置XML文件名
/// </summary>
public const string AutoUpdateConfig_XmlFileName = "Au";
/// <summary>
/// 本地自动更新下载临时存放目录
/// </summary>
public const string TempDir = "Temp";
/// <summary>
/// 远端自动更新信息XML文件名
/// </summary>
public const string AutoUpdateInfo_XmlFileName = "Au;;
/// <summary>
/// 远端自动更新文件存放目录
/// </summary>
public const string RemoteDir = "AutoUpdateFiles";
/// <summary>
/// 主线程名
/// </summary>
public const string MainProcess = "AutoUpdaterTest";
#endregion
}
作用:应用程序上下文。
/// <summary>
/// 应用程序上下文
/// </summary>
public class AppContext
{
/// <summary>
/// 客户端配置文件
/// </summary>
public static AutoUpdateConfig AutoUpdateConfigData { get; set; }
}
作用:应用程序配置。
public class AppConfig
{
private static readonly object _lock = new object();
private static AppConfig _instance = null;
#region 自动更新配置
/// <summary>
/// 自动更新配置数据
/// </summary>
public AutoUpdateConfig AutoUpdateConfigData { get; set; }
private AppConfig()
{
AutoUpdateConfigData = new AutoUpdateConfig();
}
public static AppConfig Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new AppConfig();
}
}
}
return _instance;
}
}
/// <summary>
/// 本地自动更新下载临时文件夹路径
/// </summary>
public string TempPath
{
get
{
return ("{0}\\{1}", A, GlobalParam.TempDir);
}
}
/// <summary>
/// 初始化系统配置信息
/// </summary>
public void InitialSystemConfig()
{
Au = AppContext.Au;
Au = AppContext.Au;
}
#endregion
}
5.3、工具类
作用:配置文件的读写。
public class AutoUpdateHelper
{
private readonly string AutoUpdateMode = ;
public AutoUpdateHelper(string autoUpdateMode)
{
AutoUpdateMode = autoUpdateMode;
}
/// <summary>
/// 加载本地自动更新配置文件
/// </summary>
/// <returns></returns>
public static AutoUpdateConfig Load()
{
string filePath = , fileContent = ;
filePath = Pa(A, GlobalParam.AutoUpdateConfig_XmlFileName);
AutoUpdateConfig config = new AutoUpdateConfig();
fileContent = FileU(filePath);
object obj = XmlU(typeof(AutoUpdateConfig), fileContent);
config = obj as AutoUpdateConfig;
return config;
}
/// <summary>
/// 获取远端自动更新信息的版本号
/// </summary>
/// <returns></returns>
public string GetRemoteAutoUpdateInfoVersion()
{
XDocument doc = new XDocument();
doc = XDocument.Parse(GetRemoteAutoUpdateInfoXml());
return doc.Element("AutoUpdateInfo").Element("NewVersion").Value;
}
/// <summary>
/// 获取远端自动更新信息的XML文件内容
/// </summary>
/// <returns></returns>
public string GetRemoteAutoUpdateInfoXml()
{
string remoteXmlAddress = AppConfig.Instance.Au + "/" + GlobalParam.AutoUpdateInfo_XmlFileName;
string receiveXmlPath = Pa, GlobalParam.AutoUpdateInfo_XmlFileName);
string xmlString = ;
if ) == false)
{
Direc);
}
if () == "HTTP")
{
WebClient client = new WebClient();
client.DownloadFile(remoteXmlAddress, receiveXmlPath);
}
if (receiveXmlPath))
{
xmlString = FileU(receiveXmlPath);
return xmlString;
}
return ;
}
/// <summary>
/// 写入本地自动更新配置的XML文件内容
/// </summary>
/// <returns></returns>
public string WriteLocalAutoUpdateInfoXml()
{
string xmlPath = , xmlValue = ;
xmlPath = Pa, GlobalParam.AutoUpdateConfig_XmlFileName);
xmlValue = XmlU(typeof(AutoUpdateConfig), A);
if (xmlPath))
{
File.Delete(xmlPath);
}
bool blSuccess = FileU(xmlPath, xmlValue);
return blSuccess == true ? xmlPath : "";
}
}
5.4、本地配置文件
作用:配置自动更新模式及相关。
注1:复制到输出目录选择始终复制。
注2:主程序运行时,先读取此配置更新文件,然后给AppContext上下文赋值,接着给AppConfig配置赋值。
<?xml version="1.0" encoding="utf-8" ?>
<AutoUpdateConfig>
<!--自动升级模式:当前仅支持HTTP-->
<AutoUpdateMode>HTTP</AutoUpdateMode>
<!--HTTP自动升级模式时的URL地址-->
<AutoUpdateHttpUrl>;/AutoUpdateHttpUrl>
</AutoUpdateConfig>
5.5、主程序
新建一个Windows 窗体MainForm,此处理仅需要一个空白窗体即可,作测试用。
public partial class MainForm : Form
{
private static MainForm _Instance;
/// <summary>
/// MainForm主窗体实例
/// </summary>
public static MainForm Instance
{
get
{
if (_Instance == null)
{
_Instance = new MainForm();
}
return _Instance;
}
}
public MainForm()
{
InitializeComponent();
}
}
5.6、应用程序主入口
作用:检测应用程序是否需要自动更新,如里需要则检测远程服务端的版本号。假如远程服务端有新版本,则调用自动更新器AutoUpdater并向其传递4个参数。
internal static class Program
{
/// <summary>
/// 应用程序的主入口点
/// </summary>
[STAThread]
private static void Main(string[] args)
{
//尝试设置访问权限
FileU("Users", A);
//未捕获的异常处理
A += Application_ThreadException;
A);
A += CurrentDomain_UnhandledException;
//是否检查自动更新赋值
if > 0)
{
GlobalParam.CheckAutoUpdate = args[0];
}
//加载自动更新配置文件,给上下文AppServiceConfig对象赋值。
var config = Au();
A = config;
//窗体互斥体
var instance = new Mutex(true, GlobalParam.MainProcess, out bool isNewInstance);
if (isNewInstance == true)
{
if ())
{
if (CheckUpdater())
Proce);
}
A();
A(false);
A);
in();
}
else
{
Me("已经启动了一个程序,请先退出。", "提示", Me, Me);
A();
}
}
/// <summary>
/// 自动更新检测
/// </summary>
/// <returns></returns>
private static bool CheckUpdater()
{
if () == false) return false;
#region 检查版本更新
Stopwatch stopwatch = new Stopwatch();
();
bool blFinish = false;
A();
var helper = new AutoUpdateHelper(AppConfig.Instance.Au);
string fileVersion = FileVer).FileVersion;
long localVersion = 0;
long remoteVersion = 0;
try
{
localVersion = (".", "").ToLong();
remoteVersion = ().Replace(".", "").ToLong();
if ((localVersion > 0) && (localVersion < remoteVersion))
{
blFinish = true;
string autoUpdateConfigXmlPath = ();
string autoUpdateInfoXmlPath = Pa, GlobalParam.AutoUpdateInfo_XmlFileName);
string argument1 = autoUpdateConfigXmlPath;
string argument2 = autoUpdateInfoXmlPath;
string argument3 = GlobalParam.MainProcess;
string argument4 = GlobalParam.RemoteDir;
string arguments = argument1 + " " + argument2 + " " + argument3 + " " + argument4;
Proce("Au;, arguments);
A();
}
}
catch (TimeoutException)
{
blFinish = false;
}
catch (WebException)
{
blFinish = false;
}
catch (Exception)
{
blFinish = false;
}
return blFinish;
#endregion
}
/// <summary>
/// UI线程未捕获异常处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = Da() + " 出现应用程序未处理的异常:\r\n";
var error = e.Exception;
if (error != null)
{
strError = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}";
strLog = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}\r\n来源信息:{error.Source}\r\n";
}
else
{
strError = $"Application ThreadException:{e}";
}
WriteLog(strLog);
Me(strError, "系统错误", Me, Me);
}
/// <summary>
/// 非UI线程未捕获异常处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = Da() + " 出现应用程序未处理的异常:\r\n";
if is Exception error)
{
strError = strDateInfo + $"异常消息:{error.Message}";
strLog = strDateInfo + $"异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}";
}
else
{
strError = $"Application UnhandledError:{e}";
}
WriteLog(strLog);
Me(strError, "系统错误", Me, Me);
}
/// <summary>
/// 写入日志
/// </summary>
/// <param name="strLog"></param>
private static void WriteLog(string strLog)
{
string dirPath = @"Log\MainProcess", fileName = Da("yyyy-MM-dd") + ".txt";
string strLine = "----------------------------------------------------------------------------------------------------";
FileU(Pa(dirPath, fileName), strLog);
FileU(Pa(dirPath,fileName), strLine);
}
}
六、AutoUpdater
6.1、实体类
作用:配置自动更新模式及相关。
/// <summary>
/// 自动更新配置信息
/// </summary>
public class AutoUpdateConfig
{
/// <summary>
/// 自动升级模式:当前仅支持HTTP
/// </summary>
public string AutoUpdateMode { get; set; }
/// <summary>
/// HTTP自动升级模式时的URL地址
/// </summary>
public string AutoUpdateHttpUrl { get; set; }
}
作用:自动更新内容信息。
/// <summary>
/// 自动更新内容信息
/// </summary>
[Serializable]
public class AutoUpdateInfo
{
/// <summary>
/// 新版本号
/// </summary>
public string NewVersion { get; set; }
/// <summary>
/// 更新日期
/// </summary>
public string UpdateTime { get; set; }
/// <summary>
/// 更新内容说明
/// </summary>
public string UpdateContent { get; set; }
/// <summary>
/// 更新文件列表
/// </summary>
public List<string> FileList { get; set; }
}
6.2、通用类
作用:应用程序全局静态常量。全局参数都在此设置,方便统一管理。
/// <summary>
/// 应用程序全局静态常量
/// </summary>
public static class GlobalParam
{
/// <summary>
/// 调用程序主线程名称
/// </summary>
public static string MainProcess = ;
/// <summary>
/// 远程更新程序所在文件夹的名称
/// </summary>
public static string RemoteDir = ;
}
6.3、Window 窗体
新建一个Windows 窗体HttpStartUp。
public partial class HttpStartUp : Form
{
private bool _blSuccess = false;
private string _autoUpdateHttpUrl = null;
private AutoUpdateInfo _autoUpdateInfo = null;
public HttpStartUp(string autoUpdateHttpUrl, AutoUpdateInfo autoUpdateInfo)
{
InitializeComponent();
_autoUpdateHttpUrl = autoUpdateHttpUrl;
_autoUpdateInfo = autoUpdateInfo;
_blSuccess = false;
}
/// <summary>
/// 窗体加载事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Main_Load(object sender, EventArgs e)
{
Text = GlobalParam.MainProcess + "-更新程序";
lblU = _au;
lblNewVer = _au;
= _au;
}
/// <summary>
/// 立即更新
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRun_Click(object sender, EventArgs e)
{
Proce);
b = false;
b = false;
Thread thread = new Thread(() =>
{
try
{
var downFileList = _au(s => s.IndexOf("\\"));
foreach (var fileName in downFileList)
{
string fileUrl = , fileVaildPath = ;
if ("\\"))
{
fileVaildPath = ("\\"));
}
else
{
fileVaildPath = fileName;
}
fileUrl = _au(new char[] { '/' }) + @"/" + GlobalParam.RemoteDir + @"/" + ("\\", "/"); //替换文件目录中的路径为网络路径
DownloadFileDetail(fileUrl, fileName);
}
_blSuccess = true;
}
catch (Exception ex)
{
BeginInvoke(new MethodInvoker(() =>
{
throw ex;
}));
}
finally
{
BeginInvoke(new MethodInvoker(delegate ()
{
b = true;
b = true;
}));
}
if (_blSuccess)
{
Proce + ".exe");
BeginInvoke(new MethodInvoker(delegate ()
{
Close();
A();
}));
}
})
{
IsBackground = true
};
();
}
private void DownloadFileDetail(string httpUrl, string filename)
{
string fileName = A + "\\" + filename;
string dirPath = GetDirPath(fileName);
if (!Direc(dirPath))
{
Direc(dirPath);
}
HttpWebRequest request = (HttpWebReque(httpUrl);
HttpWebResponse response = (HttpWebResponse();
Stream httpStream = re();
long totalBytes = re;
if (progressBar != null)
{
BeginInvoke(new MethodInvoker(delegate ()
{
lblDownIn = "开始下载...";
= (int)totalBytes;
= 0;
}));
}
FileStream outputStream = new FileStream(fileName, FileMode.Create);
int bufferSize = 2048;
int readCount;
byte[] buffer = new byte[bufferSize];
readCount = (buffer, 0, bufferSize);
int allByte = (int)re;
int startByte = 0;
BeginInvoke(new MethodInvoker(delegate ()
{
= allByte;
= 0;
}));
while (readCount > 0)
{
ou(buffer, 0, readCount);
readCount = (buffer, 0, bufferSize);
startByte += readCount;
BeginInvoke(new MethodInvoker(delegate ()
{
lblDownIn = "已下载:" + startByte / 1024 + "KB/" + "总长度:"+ allByte / 1024 + "KB" + " " + " 文件名:" + filename;
= startByte;
}));
A();
T(5);
}
BeginInvoke(new MethodInvoker(delegate ()
{
lblDownIn = "下载完成。";
}));
();
ou();
re();
}
public static string GetDirPath(string filePath)
{
if ("\\") > 0)
{
return (0, ("\\"));
}
return filePath;
}
/// <summary>
/// 暂不更新
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLeave_Click(object sender, EventArgs e)
{
if (Me("确定要放弃此次更新吗?", "提示", Me, Me) == DialogRe)
{
Proce + ".exe", "false");
Close();
A();
}
}
}
6.4、应用程序主入口
internal static class Program
{
/// <summary>
/// 应用程序的主入口点
/// </summary>
[STAThread]
private static void Main(string[] args)
{
A);
A += new Sy(Application_ThreadException);
A += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
A();
A(false);
#region 测试
//string strArgs = @"E:\LinkTo.AutoUpdater\AutoUpdaterTest\bin\Debug\Temp\Au"+" "+@"E:\LinkTo.AutoUpdater\AutoUpdaterTest\bin\Debug\Temp\Au;+" "+"AutoUpdaterTest"+" "+"AutoUpdateFiles";
//args = (' ');
#endregion
if > 0)
{
string autoUpdateConfigXmlPath = args[0].ToString();
string autoUpdateInfoXmlPath = args[1].ToString();
GlobalParam.MainProcess = args[2].ToString();
GlobalParam.RemoteDir = args[3].ToString();
var autoUpdateConfigXml = FileU(autoUpdateConfigXmlPath);
var autoUpdateInfoXml = FileU(autoUpdateInfoXmlPath);
AutoUpdateConfig config = XmlU(typeof(AutoUpdateConfig), autoUpdateConfigXml) as AutoUpdateConfig;
AutoUpdateInfo info = XmlU(typeof(AutoUpdateInfo), autoUpdateInfoXml) as AutoUpdateInfo;
if () == "HTTP")
{
A(new HttpStartU, info));
}
}
else
{
A();
}
}
/// <summary>
/// UI线程未捕获异常处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void Application_ThreadException(object sender, Sy e)
{
string strError = "", strLog = "", strDateInfo = Da() + " 出现应用程序未处理的异常:\r\n";
var error = e.Exception;
if (error != null)
{
strError = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}";
strLog = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}\r\n来源信息:{error.Source}\r\n";
}
else
{
strError = $"Application ThreadException:{e}";
}
WriteLog(strLog);
Me(strError, "系统错误", Me, Me);
}
/// <summary>
/// 非UI线程未捕获异常处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = Da() + " 出现应用程序未处理的异常:\r\n";
if is Exception error)
{
strError = strDateInfo + $"异常消息:{error.Message}";
strLog = strDateInfo + $"异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}";
}
else
{
strError = $"Application UnhandledError:{e}";
}
WriteLog(strLog);
Me(strError, "系统错误", Me, Me);
}
/// <summary>
/// 写入日志
/// </summary>
/// <param name="strLog"></param>
private static void WriteLog(string strLog)
{
string dirPath = @"Log\AutoUpdater", fileName = Da("yyyy-MM-dd") + ".txt";
string strLine = "----------------------------------------------------------------------------------------------------";
FileU(Pa(dirPath, fileName), strLog);
FileU(Pa(dirPath, fileName), strLine);
}
}
七、AutoUpdateXmlBuilder
7.1、实体类
作用:自动更新内容信息。
/// <summary>
/// 自动更新内容信息
/// </summary>
[Serializable]
public class AutoUpdateInfo
{
/// <summary>
/// 新版本号
/// </summary>
public string NewVersion { get; set; }
/// <summary>
/// 更新日期
/// </summary>
public string UpdateTime { get; set; }
/// <summary>
/// 更新内容说明
/// </summary>
public string UpdateContent { get; set; }
/// <summary>
/// 更新文件列表
/// </summary>
public List<string> FileList { get; set; }
}
7.2、通用类
作用:应用程序全局静态常量。全局参数都在此设置,方便统一管理。
/// <summary>
/// 应用程序全局静态常量
/// </summary>
public static class GlobalParam
{
/// <summary>
/// 远端自动更新信息XML文件名
/// </summary>
public const string AutoUpdateInfo_XmlFileName = "Au;;
/// <summary>
/// 远端自动更新目录
/// </summary>
public const string AutoUpdateDir = "AutoUpdateDir";
/// <summary>
/// 远端自动更新文件存放目录
/// </summary>
public const string RemoteDir = "AutoUpdateFiles";
/// <summary>
/// 主线程名
/// </summary>
public const string MainProcess = "AutoUpdaterTest";
}
7.3、Window 窗体
1)新建一个Windows 窗体Main。
public partial class Main : Form
{
//自动更新目录路径
private static readonly string AutoUpdateDirPath = A + @"\" + GlobalParam.AutoUpdateDir;
//自动更新信息XML文件路径
private static readonly string AutoUpdateInfoXmlPath = Pa(AutoUpdateDirPath, GlobalParam.AutoUpdateInfo_XmlFileName);
//自动更新文件目录路径
private static readonly string RemoteDirPath = A + @"\" + GlobalParam.AutoUpdateDir + @"\" + GlobalParam.RemoteDir;
public Main()
{
InitializeComponent();
}
/// <summary>
/// 窗体加载
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Main_Load(object sender, EventArgs e)
{
if (!Direc(RemoteDirPath))
{
Direc(RemoteDirPath);
}
LoadBaseInfo();
LoadDirectoryFileList();
}
/// <summary>
/// 刷新
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRefresh_Click(object sender, EventArgs e)
{
LoadBaseInfo();
LoadDirectoryFileList();
}
/// <summary>
/// 初始化
/// </summary>
private void LoadBaseInfo()
{
dtU = Da("yyyy-MM-dd");
txtNewVer = GetMainProcessFileVersion();
CreateHeaderAndFillListView();
}
/// <summary>
/// 获取主程序文件版本
/// </summary>
/// <returns></returns>
private string GetMainProcessFileVersion()
{
string fileVersion = "";
if (RemoteDirPath + "\\" + GlobalParam.MainProcess + ".exe")) //如果更新中有主程序文件
{
FileVersionInfo info = FileVer(RemoteDirPath + "\\" + GlobalParam.MainProcess + ".exe");
fileVersion = in;
}
return fileVersion;
}
/// <summary>
/// 添加ListView列名
/// </summary>
private void CreateHeaderAndFillListView()
{
l();
int lvWithd = l;
ColumnHeader columnHeader;
//First Header
columnHeader = new ColumnHeader
{
Text = "#",
Width = 38
};
l(columnHeader);
//Second Header
columnHeader = new ColumnHeader
{
Text = "文件名",
Width = (lvWithd - 38) / 2
};
l(columnHeader);
//Third Header
columnHeader = new ColumnHeader
{
Text = "更新路径",
Width = (lvWithd - 38) / 2
};
l(columnHeader);
}
/// <summary>
/// 加载目录文件列表
/// </summary>
private void LoadDirectoryFileList()
{
if (!Direc(RemoteDirPath))
{
Direc(RemoteDirPath);
}
FileHelper fileHelper = new FileHelper(RemoteDirPath);
var fileArrary = (RemoteDirPath)).ToList();
var lastFile = (s => s == GlobalParam.MainProcess + ".exe");
//exe作为最后的文件更新,防止更新过程中出现网络错误,导致文件未全部更新。
if (lastFile != null)
{
(lastFile);
(lastFile);
}
PopulateListViewWithArray());
}
/// <summary>
/// 使用路径字符数组填充列表
/// </summary>
/// <param name="strArray"></param>
private void PopulateListViewWithArray(string[] strArray)
{
l();
if (strArray != null)
{
//只过滤根目录下的特殊文件
strArray = (s => !new string[] { GlobalParam.AutoUpdateInfo_XmlFileName }.Contain('\\') + 1))).ToArray();
for (int i = 0; i < ; i++)
{
ListViewItem lvi = new ListViewItem
{
Text = (i + 1).ToString()
};
int intStart = strArray[i].LastIndexOf('\\') + 1;
lvi.SubI(strArray[i].Substring(intStart, strArray[i].Length - intStart));
lvi.SubI(strArray[i]);
lstFileList.I(lvi);
}
}
}
/// <summary>
/// 生成更新XML文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnBuild_Click(object sender, EventArgs e)
{
if (txtNewVer))
{
Me("更新版本号不能为空。", "提示", Me, Me);
();
return;
}
if ))
{
Me("主进程名不能为空。", "提示", Me, Me);
();
return;
}
AutoUpdateInfo info = new AutoUpdateInfo()
{
NewVersion = txtNewVer.Trim(),
UpdateTime = d("yyyy-MM-dd"),
UpdateContent = ,
FileList = l;ListViewItem>().Select(s => s.SubItems[2].Text).ToList()
};
string xmlValue = XmlU(typeof(AutoUpdateInfo), info);
using (StreamWriter sw = new StreamWriter(AutoUpdateInfoXmlPath))
{
(xmlValue);
();
();
}
Me("生成成功。", "提示", Me, Me);
}
/// <summary>
/// 打开本地目录
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOpen_Click(object sender, EventArgs e)
{
ProcessStartInfo psi = new ProcessStartInfo("Ex;)
{
Arguments = AutoUpdateDirPath
};
Proce(psi);
}
}
2)在bin\Debug\下新建一个AutoUpdateDir文件夹,然后再在AutoUpdateDir下新建一个AutoUpdateFiles文件夹。
3)在AutoUpdaterTest中,将程序集版本及文件版本都改成1.0.0.1并重新生成,接着将Au拷贝到AutoUpdateFiles下,最后将程序集版本及文件版本都改回1.0.0.0。
4)此时运行AutoUpdateXmlBuilder,点击生成更新XML文件即打包成功。程序会自动在AutoUpdateDir下生成打包信息文件Au。
7.4、应用程序主入口
internal static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
private static void Main()
{
A);
A += new Sy(Application_ThreadException);
A += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
A();
A(false);
A(new Main());
}
/// <summary>
/// UI线程未捕获异常处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void Application_ThreadException(object sender, Sy e)
{
string strError = "", strLog = "", strDateInfo = Da() + " 出现应用程序未处理的异常:\r\n";
var error = e.Exception;
if (error != null)
{
strError = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}";
strLog = strDateInfo + $"异常类型:{error.GetType().Name}\r\n异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}\r\n来源信息:{error.Source}\r\n";
}
else
{
strError = $"Application ThreadException:{e}";
}
WriteLog(strLog);
Me(strError, "系统错误", Me, Me);
}
/// <summary>
/// 非UI线程未捕获异常处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string strError = "", strLog = "", strDateInfo = Da() + " 出现应用程序未处理的异常:\r\n";
if is Exception error)
{
strError = strDateInfo + $"异常消息:{error.Message}";
strLog = strDateInfo + $"异常消息:{error.Message}\r\n堆栈信息:{error.StackTrace}";
}
else
{
strError = $"Application UnhandledError:{e}";
}
WriteLog(strLog);
Me(strError, "系统错误", Me, Me);
}
/// <summary>
/// 写入日志
/// </summary>
/// <param name="strLog"></param>
private static void WriteLog(string strLog)
{
string dirPath = @"Log\XmlBuilder", fileName = Da("yyyy-MM-dd") + ".txt";
string strLine = "----------------------------------------------------------------------------------------------------";
FileU(Pa(dirPath, fileName), strLog);
FileU(Pa(dirPath, fileName), strLine);
}
}
八、远程服务端配置
注:此处为本机测试。
1)在某个盘符如E盘下新建一个AutoUpdate文件夹,将AutoUpdateXmlBuilder打包文件夹AutoUpdateDir拷贝到AutoUpdate文件夹下。
2)在IIS中新建一个网站,对应的虚拟目录为E:\AutoyuaUpdate,同时将端口设置为6600。
3)运行AutoUpdaterTest,如出现自动更新器即代表成功。
源码下载: