分析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. ...
随机推荐
- Domino----The Address Book does not contain a cross certificate capable of validating the public key.
The Address Book does not contain a cross certificate capable of validating the public key. 地址本不包含交叉 ...
- xml_TO_object
一般对于开发人员拿到的xml文件都是配置文件,所以对于我们来说,最主要要做的事情是将xml的内容封装成对象. 下面展示代码 package javaDom4j; import java.util.Ar ...
- 一个神奇的POS -扫描 现场销售 开单打印票据 安卓物联网POS机 手持开单终端机 省时省力 高效准确!!
5寸高清彩屏,高端大气上档次,小巧轻便,独特的包胶防护,坚固耐用,外形精细,美观!与软件灵活对接,解决企业手工盘点,手工输单,库存管理等困难,提高准确率,提高工作效率!! 应用领域:适用于仓库.超市. ...
- 矩阵快速幂 HDU 4565 So Easy!(简单?才怪!)
题目链接 题意: 思路: 直接拿别人的图,自己写太麻烦了~ 然后就可以用矩阵快速幂套模板求递推式啦~ 另外: 这题想不到或者不会矩阵快速幂,根本没法做,还是2013年长沙邀请赛水题,也是2008年Go ...
- 打包如何打包额外文件,比如Sqlite数据库的db文件
http://aigo.iteye.com/blog/2278224 Project Settings -> packaging -> Packaging选项中,有多个设置项来设置打包时要 ...
- yarn关于app max attempt深度解析,针对长服务appmaster平滑重启
在YARN上开发长服务,需要注意fault-tolerance,本篇文章对appmaster的平滑重启的一个参数做了解析,如何设置可以有助于达到appmaster平滑重启. 在yarn-site.xm ...
- UVALive-4839 HDU-3686 Traffic Real Time Query System 题解
题目大意: 有一张无向连通图,问从一条边走到另一条边必定要经过的点有几个. 思路: 先用tarjan将双连通分量都并起来,剩下的再将割点独立出来,建成一棵树,之后记录每个点到根有几个割点,再用RMQ求 ...
- Redis 主从配置
环境 Master/Slave 系统 IP Redis版本 Master CentOS6.7 10.10.3.211 redis-3.2.6 ...
- Django--全文检索功能
经过两个月的时间,毕设终于算是把所有主要功能都完成了,最近这一周为了实现全文检索的功能,也算是查阅了不少资料,今天就在这里记录一下,以免以后再用到时抓瞎了~ 首先介绍一下我使用的Django全文检索逻 ...
- About_Smarty
Smarty是一个使用PHP写出来的模板PHP模板引擎,是目前业界最著名的PHP模板引擎之一.它分离了逻辑代码和外在的内容,提供了一种易于管理和使用的方法,用来将原本与HTML代码混杂在一起PHP代码 ...