分析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. ...
随机推荐
- Flex Layout Attribute
GitHub: https://github.com/StefanKovac/flex-layout-attribute 引入基本的样式,可以更好的布局,可以在线制作: http://progress ...
- 第一天ci框架开发商城1
ci框架开发商城1 1/28/2016 9:43:52 PM userguide删除 system application controllers 控制器 models 模型 views 视图 模板 ...
- [ACM训练] 算法初级 之 搜索算法 之 广度优先算法BFS (POJ 3278+1426+3126+3087+3414)
BFS算法与树的层次遍历很像,具有明显的层次性,一般都是使用队列来实现的!!! 常用步骤: 1.设置访问标记int visited[N],要覆盖所有的可能访问数据个数,这里设置成int而不是bool, ...
- 数据泵Expdp和Impdp
一.数据泵导入导出技术 1.结构 2.目录对象 二.EXPDP参数 1.attach 2.content 3.directory 4.dumpfile 5.estimate 6.estimate_on ...
- BZOJ4597: [Shoi2016]随机序列
Description 你的面前有N个数排成一行.分别为A1, A2, … , An.你打算在每相邻的两个 Ai和 Ai+1 间都插入一个加号或者 减号或者乘号.那么一共有 3^(n-1) 种可能的表 ...
- MYSQL的安装
1.将mysql的安装文件放入虚拟机 2.搭建yum库 3.依次安装mysql的5个文件 最后一个server需要的依赖太多,所以用yum进行安装. 6.进行mysql的重置 mysql_instal ...
- 出现了内部错误-网站中X509Certificate2加载证书时出错
今天给网站配置了加密证书文件,用类X509Certificate2加载证书文件时,一直报出现了内部错误,但是Demo中用控制台程序加载证书没任何问题 读取证书文件的语句: X509Certificat ...
- sql筛选查询A表中B表已经存在的数据
SELECT *FROM A LEFT OUTER JOIN B ON A.ID = B.IDWHERE B.ID IS NULL 开发实例: SELECT Position_Car.Area, Po ...
- 通读SDWebImage①--总体梳理、下载和缓存
本文目录 下载操作SDWebImageDownloaderOptions和下载过程实现 下载管理SDWebImageDownloader 缓存SDImageCache SDWebImageManage ...
- 深入理解css BFC 模型
如果要深入理解css布局的各种原理,要在重构页面做得心应手的话,那么你就必须先理解这玩意 "BFC" , BlockFormatting Context(块级格式化上下文): 这里 ...