分析nuget源码,用nuget + nuget.server实现winform程序的自动更新
源起
(个人理解)包管理最开始应该是从java平台下的maven开始吧,因为java的开发大多数是基于开源组件开发的,一个开源包在使用时很可能要去依赖其他的开源包,而且必须是特定的版本才可以。以往在找到一个开源包后,往往要用很多时间去把依赖的包找齐,于是maven出现了,它能自动搜索一个包的依赖项并下载到本地,免去找各种引用包的时间。
在maven出现不久后,.net也出现了自己的包管理工具,nuget,相信园子里的人都有所了解,nuget的官方源和microsoft源上集成了很多开源组件,供大家使用,而且在下载过程会进行相应解析,下载对应的依赖包。
上面是对包管理的一些介绍,理解包管理,那么很容易想到,有没有可能用包管理现成的组件来开发一个面向程序的自动更新?
主要有以下的好处:
1.更新的服务器端是现成的(nuget.server,nuget.galley)
2.发布工具是责成的(nuget command)
那么,主要就是要完成自动更新部分的检测,下载,以及解析。
先分析一下VS中包管理的方式:
1.所有包都维护在项目下的packages.config文件中;
2.在检测更新时,会连接到服务器上去进行检测(不同的包源)
3.要下载包
4.在包下载后,要将包解开,加到工程引用中;
那么,我们读源码的工作,主要如下:
1.理解怎么通过packages.config文件得到包的引用
2.得到包的引用后,如何去检测更新
3.怎么对包进行解析
下面和大家分享我的做法。
1.理解源码的第一步,需要懂得nuget.core中是怎么对这个packages.config进行解析,按照这种思路,在nuget.core中找到PackageReferenceFile这个类(直接全工程搜“package.config",最后定位于此)
namespace NuGet
{
public class PackageReferenceFile
{
public PackageReferenceFile(string path);
public PackageReferenceFile(IFileSystem fileSystem, string path); public void AddEntry(string id, SemanticVersion version);
public void AddEntry(string id, SemanticVersion version, FrameworkName targetFramework);
public bool DeleteEntry(string id, SemanticVersion version);
public bool EntryExists(string packageId, SemanticVersion version);
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
public IEnumerable<PackageReference> GetPackageReferences();
public IEnumerable<PackageReference> GetPackageReferences(bool requireVersion);
public void MarkEntryForReinstallation(string id, SemanticVersion version, FrameworkName targetFramework, bool requireReinstallation);
}
}
注意到一个很直接的构造函数
namespace NuGet
{
public class PackageReferenceFile
{
public PackageReferenceFile(string path);
//other codes
}
}
在本地调用一下,发现成功能生成PackageReferenceFile类,同时用GetPackageReferences,能够得到一个IEnumerable<PackageReference>
继续代码,查到PackageReference定义:
namespace NuGet
{
public class PackageReference : IEquatable<PackageReference>
{
public PackageReference(string id, SemanticVersion version, IVersionSpec versionConstraint, FrameworkName targetFramework, bool isDevelopmentDependency, bool requireReinstallation = false); public string Id { get; }
public bool IsDevelopmentDependency { get; }
public bool RequireReinstallation { get; }
public FrameworkName TargetFramework { get; }
public SemanticVersion Version { get; }
public IVersionSpec VersionConstraint { get; set; } public override bool Equals(object obj);
public bool Equals(PackageReference other);
public override int GetHashCode();
public override string ToString();
}
}
可以看到,在这个类中包的ID,版本都有对应的属性来表达,那么这应该就是我们可以用来解析包引用的类,这样,我们第一步工作已经完成了,通过解析本地的文件得到了包的引用关系。
2.第二步,要理解怎么去检测更新。第一个直观的想法是查查有没有包函类似于update,getupdate方法的类,或者是接口,成功的找到最终的接口 IServiceBasedRepository
namespace NuGet
{
public interface IServiceBasedRepository : IPackageRepository
{
IEnumerable<IPackage> GetUpdates(IEnumerable<IPackage> packages, bool includePrerelease, bool includeAllVersions, IEnumerable<System.Runtime.Versioning.FrameworkName> targetFrameworks, IEnumerable<IVersionSpec> versionConstraints);
IQueryable<IPackage> Search(string searchTerm, IEnumerable<string> targetFrameworks, bool allowPrereleaseVersions);
}
}
再查找实现这个接口的类,OK,我幸运的找到了表示服务器资源的类DataServicePackageRepository
namespace NuGet
{
public class DataServicePackageRepository : PackageRepositoryBase, IHttpClientEvents, IProgressProvider, IServiceBasedRepository, ICloneableRepository, ICultureAwareRepository, IOperationAwareRepository, IPackageLookup, IPackageRepository, ILatestPackageLookup, IWeakEventListener
{
public DataServicePackageRepository(IHttpClient client);
public DataServicePackageRepository(Uri serviceRoot);
public DataServicePackageRepository(IHttpClient client, PackageDownloader packageDownloader); public CultureInfo Culture { get; }
public PackageDownloader PackageDownloader { get; }
public override string Source { get; }
public override bool SupportsPrereleasePackages { get; } public event EventHandler<ProgressEventArgs> ProgressAvailable;
public event EventHandler<WebRequestEventArgs> SendingRequest; public IPackageRepository Clone();
public bool Exists(string packageId, SemanticVersion version);
public IPackage FindPackage(string packageId, SemanticVersion version);
public IEnumerable<IPackage> FindPackagesById(string packageId);
public override IQueryable<IPackage> GetPackages();
public IEnumerable<IPackage> GetUpdates(IEnumerable<IPackage> packages, bool includePrerelease, bool includeAllVersions, IEnumerable<System.Runtime.Versioning.FrameworkName> targetFrameworks, IEnumerable<IVersionSpec> versionConstraints);
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e);
public IQueryable<IPackage> Search(string searchTerm, IEnumerable<string> targetFrameworks, bool allowPrereleaseVersions);
public IDisposable StartOperation(string operation, string mainPackageId, string mainPackageVersion);
public bool TryFindLatestPackageById(string id, out SemanticVersion latestVersion);
public bool TryFindLatestPackageById(string id, bool includePrerelease, out IPackage package);
}
}
这里面有两个方法一眼可以得知,一个是GetUpdates方法,显而易见,是查到有更新的包
另一个是构造函数 DataServicePackageRepository(Uri serviceRoot),即以nuget的源地址初始化,但是有一个问题,我们目前得到的是PackageReference,而函数里要调用的是IPackage,它的定义如下:
namespace NuGet
{
public interface IPackage : IPackageMetadata, IServerPackageMetadata
{
IEnumerable<IPackageAssemblyReference> AssemblyReferences { get; }
bool IsAbsoluteLatestVersion { get; }
bool IsLatestVersion { get; }
bool Listed { get; }
DateTimeOffset? Published { get; } [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
IEnumerable<IPackageFile> GetFiles();
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
Stream GetStream();
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
IEnumerable<System.Runtime.Versioning.FrameworkName> GetSupportedFrameworks();
}
}
同时,我又看了IPackage的每一个类(在源码中),没有一个可以从PackageReference直接进行构造,而其他的逻辑又太复杂(我比较懒哈),怎么办?看了一下GetUpdates的源码,发现在检测更新时只用到了Package类里的两个字段, 即id和version,OK,那么这样就好办了,我们自己定义一个IPackge的实现,只要实现id和version就可以:
class TempPackage :NuGet.IPackage
{
public string Id
{
get;
internal set;
} public NuGet.SemanticVersion Version
{
get;
internal set;
}
//other codes that not Implemented }
OK,那么得,定义了这个,我们在检测更新前进行一下转换即可:
var localFiles = File.GetPackageReferences();//File is NuGet.PackageReferenceFile
foreach (var i in localFiles)
{
localPacks.Add(new TempPackage() { Id = i.Id, Version = i.Version });
} var updatepacks = Source.GetUpdates(localPacks, false, false, null, null);//Source is DataServicePackageRepository
哈哈,至此,我们已经得到要更新的包。。 那么进入第三步,包的解析。
3.第三步,解析包,从自己定义TempPackage时,我们得到了IPackage的定义,发现有一个方法,MS不错,他是:
public interface IPackage : IPackageMetadata, IServerPackageMetadata
{
//other codes
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
IEnumerable<IPackageFile> GetFiles();
}
什么意思,可以得到包中包含的文件么?IPackageFile又是什么??
namespace NuGet
{
public interface IPackageFile : IFrameworkTargetable
{
string EffectivePath { get; }
string Path { get; }
FrameworkName TargetFramework { get; } [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
Stream GetStream();
}
}
OK,在没有读源码的情况下,抱着试一试的感觉,我写了如下的代码:
var installFiles = localfile.GetFiles();
foreach (var savefile in installFiles)
{
byte[] data = new byte[savefile.GetStream().Length];
savefile.GetStream().Read(data, , data.Length); var fileinfo = new System.IO.FileInfo(startDir + "\\" + savefile.EffectivePath);
if (System.IO.Directory.Exists(fileinfo.Directory.FullName) == false)
System.IO.Directory.CreateDirectory(fileinfo.Directory.FullName);
using (var filewrite = System.IO.File.Create(fileinfo.FullName))
{
filewrite.Write(data, , data.Length);
} }
哈哈,运行以后,我发现本地已经成功的解析出一个包里的相应文件 !!
到此,对源码的研究已经结束,下面就是按这个思路进行写软件了,至此,我们要解决的问题都已经全部完成,我又看了一下其他的部分,还有更优化的方案,即,可以用一个临时目录初始化一个LocalPackageRepository类,用服务器源和本地LocalPackageRepository的可以直接初始化PackageManager,在检测更新以后,直接用InstallPackage即可将包下载到本地。
4.更新文件的替换,因为程序在启动后,会自动加载相关的dll,那么怎么对更新文件进替换?其实很简单,用dynamic直接动态加载主窗体即可。
5.一些其他的技巧
看了一些人关于建立本地源以后自动化打包的方案,感觉都很麻烦,在一段时间摸索以后,发现要在post-build命令行中加如下命令,即可完成在编译后自动上传:
nuget pack "$(ProjectPath)" -o “本地临时目录”
nuget push 本地临时目录$(TargetName).*.nupkg apiKey -S 本地服务器包源
move 本地临时目录*.nupkg 本地包源
当然,一编译就上传这个事儿有点过份哈,不过如果版本号不改话,客户端是检测不到更新的,所以,在测试没问题时,可以将版本号进行更新,这样客户端就能检测到相应的更新了。
本人写东西的能力一般,这些源码里穿插着如此多的废话主要是想和大家分享自己去目的性研究一些代码的方法和思路,如果说的不对或是不当,还请拍砖。
附: nuget官网 http://www.nuget.org/
建立self-host包源 http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds
分析nuget源码,用nuget + nuget.server实现winform程序的自动更新的更多相关文章
- 一 分析easyswoole源码(启动服务)
分析easyswoole源码 1以启动为例 //检查是否已经安装 installCheck();//检查锁文件是否存在,不存在结束 //启动服务 serverStart showLogo();//显示 ...
- [源码分析] 从源码入手看 Flink Watermark 之传播过程
[源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...
- 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...
- k8s client-go源码分析 informer源码分析(3)-Reflector源码分析
k8s client-go源码分析 informer源码分析(3)-Reflector源码分析 1.Reflector概述 Reflector从kube-apiserver中list&watc ...
- Android源码分析--CircleImageView 源码详解
源码地址为 https://github.com/hdodenhof/CircleImageView 实际上就是一个圆形的imageview 的自定义控件.代码写的很优雅,实现效果也很好, 特此分析. ...
- 分析jQuery源码时记录的一点感悟
分析jQuery源码时记录的一点感悟 1. 链式写法 这是jQuery语法上的最大特色,也许该改改POJO里的set方法,和其他的非get方法什么的,可以把多行代码合并,减去每次 ...
- Linux内核(2) - 分析内核源码如何入手(上)
透过现象看本质,兽兽们无非就是一些人体艺术展示.同样往本质里看过去,学习内核,就是学习内核的源代码,任何内核有关的书籍都是基于内核,而又不高于内核的. 既然要学习内核源码,就要经常对内核代码进行分析, ...
- STM32F103 ucLinux开发之一(BOOT分析及源码)
STM32F103 ucLinux开发BOOT STM3210E-EVAL官方开发板主芯片STM32F103ZET6: 片内512K Flash,地址0x0800 0000 ~ 0x0807 FFFF ...
- Activiti架构分析及源码详解
目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...
随机推荐
- oracle函数简析
(一).数值型函数(Number Functions) 数值型函数输入数字型参数并返回数值型的值.多数该类函数的返回值支持38位小数点,诸如:COS, COSH, EXP, LN, LOG, SIN, ...
- android中接口和抽象类的区别
最近发现很多基础有点生疏了,特地写一点博客来巩固一下.今天主要来谈谈接口和抽象类的区别,我们在项目的很多地方都会用到接口或者抽象类,但是它们之间的一些区别和相同点不知道大家有没有注意到,还有就是,什么 ...
- 读取web项目properties文件路径 解决tomcat服务器找不到properties路径问题
1.需求:有时候我们产品经理给我们的需求是会不断变化的,例如数量是1000现在变成500,我们不可以去改代码吧,这样很麻烦,所以就可以改配置文件properties(这个数据库链接一样),当然也有js ...
- switch..case使用
1.多个if...else连在一起使用的时候,可以转为使用更方便的switch结构.switch (XXX) { case "aaa": // ... break; case &q ...
- 二分图&网络流&最小割等问题的总结
二分图基础: 最大匹配:匈牙利算法 最小点覆盖=最大匹配 最小边覆盖=总节点数-最大匹配 最大独立集=点数-最大匹配 网络流: 技巧: 1.拆点为边,即一个点有限制,可将其转化为边 BZOJ1066, ...
- javascript 全局对象--w3school
JavaScript全局对象 1. decodeURI()解析某个编码的URI. 2.decodeURInComponent()解析一个编码的URI组件. 3.encodeURI()把字符串编码为U ...
- QGis、Qt对话框上的OK、Open、Cancel、Help等英文翻译
成功编译qgis,启动程序发现对话框上的OK.Open.Cancel.Help等依然是英文字段,然后查找源码看这些字段是否都添加到了语言翻译包中: 最后发现这些按钮都是qt的QTGui4库中的QDia ...
- struts2 框架处理流程
struts2 框架处理流程 流程图如下: 注意:StrutsPrepareAndExecuteFilter替代了2.1.3以前的FilterDispatcher过滤器,使得在执行Action之前可以 ...
- java分享第十七天-03(封装操作mysql类)
JAVA操作mysql所需jar包:mysql-connector-java.jar代码: import java.sql.*; import java.util.ArrayList; import ...
- java 过滤表情符号
/** * 过滤表情符号 * @create by ldw on 2016-10-25 * @param str * @return str(去掉表情符号的字符串) * @version 1.0 * ...