Visual Studio对程序集签名时一个很不好用的地方

由于我们的项目底层使用到一个通过LogicalCallContext实现的上下文数据管理框架,导致所有的Unit Test不能正常运行。具体的现象在《只在UnitTest和WebHost中的出现的关于LogicalCallContext的严重问题》有过详细的介绍。解决的方案就是对相关的程序集进行强签名,并加到GAC中,是Unit Test能够识别基于LogicalCallContext项目的类型。有了Visual Studio这个强大的IDE,程序集的签名工作很好实现——仅仅需要在Project的Properties对象框的Signing Tab中指定一个Key File就可以了。但是,Visual Studio做得不够好。

一、Visual Studio会自作主张地在项目根目录下复制一个Key File

image 举个例子,假设一个解决方案中具有两个项目:Lib1和Lib2。现在我们需要使用“同一个Key File”对Lib1和Lib2进行签名,Lib1、Lib2和Key File(Key.snk) 对应的目录结构如右图所示:Key.snk和Lib1和Lib2处在相同的目录下面。

image现在我们右击Lib1项目文件,选择Properties菜单项进行项目属性对话框,选择Signing Tab页进行程序集签名相关设置。选中Sign the assembly复选框,在下拉框中选择<Browse>选项,并在弹出的文件选择对话框中我们的Key File:Key.snk。

image 但是当你选中Key.snk这个文件的时候,Visual Studio并不会用将这个文件作为对本程序集进行签名的Key File,而是会自作主张地将该文件拷贝到Lib1所在的根目录下。最终被用于程序集签名的不是我们希望的那个File Key,而是该File Key的复制品(如右图所示)。

我不太明白微软如此设计具有怎样的考虑,但是对于我们目前的项目来说,我是无法接受的。上面的例子中只有两个需要签名的项目,就需要维护两个Key File,但是我们的项目中有数十个项目,就意味着需要维护数十个不同的Key File,从维护的角度讲,如果有朝一日我需要更换另一个Key File, 我就需要为每个项目进行更新。

那么我们有没有办法让所有项目采用同一个Key File进行签名呢?当然有,不然我也不会写这篇文章了。总的来说,我们三种不同的解决方案。

解决方案1:通过AssemblyKeyFileAttribute特性指定Key File

AssemblyKeyFileAttribute特性定义在System.Reflection命名空间下,专门用于指定在对项目进行强签名时采用的Key File。所以我们只需要在AssemblyInfo.cs中(也可以在其它地方)指定我们采用的Key File文件路径即可。通过下面的代码,我们指定我们对Lib1项目指定了我真正期望用于进行签名的那个Key File。

   1: [assembly: AssemblyVersion("1.0.0.0")]
   2: [assembly: AssemblyFileVersion("1.0.0.0")]
   3: [assembly: AssemblyKeyFile("..\\Key.snk")]

image 但是,这并不是一种推荐的Key File指定方式。当你添加了AssemblyKeyFileAttribute特性的时候,Visual Studio会有如下一个警告:“Use command line option '/keyfile' or appropriate project settings instead of 'AssemblyKeyFile'”。提示你采用另外两种方案:命令行或者项目设置。

解决方案2:通过命令行进行强签名

相信大家对通过命令行对程序集进行强签名的方式都不会感到陌生。这种方式就是直接使用.NET Framework为我们提供的强名称工具(SN.exe: Strong Name Tool)。关于SN.exe相关参数设定可以参考MSDN在线文档(http://msdn.microsoft.com/en-us/library/k5b5tt23(VS.80).aspx),在这里就不再赘言介绍了。

解决方案3:以Link的方式添加Key File

这是我最初想到的办法,但是当我试验的时候将Key File添加到项目文件的Properties子目录下,导致它重新创建新的File File。但是有人评论说这个方式是可行的,所有我尝试了一下,只要将Key File以Link的方式添加到项目的根目录下就可以了。

解决方案4:还是通过项目设置(Project Setting)

还是使用文章刚开始的那种方式,直接设置项目关于签名(Signing)的相关属性。有人会说了,你不是说这种方式会导致Key File的复制吗,为何还要使用这种方式。为此,我们需要换一种思维:通过项目设置对象框对项目进行的所有设置最终都会反映在项目文件中(.csproj或者.vbproj)。虽然通过Visual Studio不能实现我们的目标,如果我们直接更新项目文件呢?实践证明,这种方案时可行的。为此,我们通过NotePad打开Lib1的项目文件Lib1.csproj,在<ProjectGroup>元素中加上一个<AssemblyOriginatorKeyFile>元素,并指定Key File的路径(..\Key.snk)即可。

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   3:   ......
   4:   <PropertyGroup>
   5:     <SignAssembly>true</SignAssembly>
   6:   </PropertyGroup>
   7:   <PropertyGroup>
   8:     <AssemblyOriginatorKeyFile>..\Key.snk</AssemblyOriginatorKeyFile>
   9:   </PropertyGroup>  
  10: </Project>


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
<VisualStudioProject> <CSHARP ProjectType = "Local" ProductVersion = "7.10.3077" SchemaVersion = "2.0" ProjectGuid = "{A5EE1DF1-9D33-4541-AE99-BC09713525FB}" > <Build> <Settings ApplicationIcon = "..\Icon\hrm.ico" AssemblyKeyContainerName = "" AssemblyName = "人力资源管理系统" AssemblyOriginatorKeyFile = "" DefaultClientScript = "JScript" DefaultHTMLPageLayout = "Grid" DefaultTargetSchema = "IE50" DelaySign = "false" OutputType = "WinExe" PreBuildEvent = "" PostBuildEvent = "" RootNamespace = "人力资源管理系统" RunPostBuildEvent = "OnBuildSuccess" StartupObject = "" > <Config Name = "Debug" AllowUnsafeBlocks = "false" BaseAddress = "285212672" CheckForOverflowUnderflow = "false" ConfigurationOverrideFile = "" DefineConstants = "DEBUG;TRACE" DocumentationFile = "" DebugSymbols = "true" FileAlignment = "4096" IncrementalBuild = "false" NoStdLib = "false" NoWarn = "" Optimize = "false" OutputPath = "bin\Debug\" RegisterForComInterop = "false" RemoveIntegerChecks = "false" TreatWarningsAsErrors = "false" WarningLevel = "4" /> <Config Name = "Release" AllowUnsafeBlocks = "false" BaseAddress = "285212672" CheckForOverflowUnderflow = "false" ConfigurationOverrideFile = "" DefineConstants = "TRACE" DocumentationFile = "" DebugSymbols = "false" FileAlignment = "4096" IncrementalBuild = "false" NoStdLib = "false" NoWarn = "" Optimize = "true" OutputPath = "bin\Release\" RegisterForComInterop = "false" RemoveIntegerChecks = "false" TreatWarningsAsErrors = "false" WarningLevel = "4" /> </Settings> <References> <Reference Name = "System" AssemblyName = "System" HintPath = "C:\WINNT\Microsoft.NET\Framework\v1.1.4322\System.dll" /> <Reference Name = "System.Data" AssemblyName = "System.Data" HintPath = "C:\WINNT\Microsoft.NET\Framework\v1.1.4322\System.Data.dll" /> <Reference Name = "System.Drawing" AssemblyName = "System.Drawing" HintPath = "C:\WINNT\Microsoft.NET\Framework\v1.1.4322\System.Drawing.dll" /> <Reference Name = "System.Windows.Forms" AssemblyName = "System.Windows.Forms" HintPath = "C:\WINNT\Microsoft.NET\Framework\v1.1.4322\System.Windows.Forms.dll" /> <Reference Name = "System.XML" AssemblyName = "System.Xml" HintPath = "C:\WINNT\Microsoft.NET\Framework\v1.1.4322\System.XML.dll" /> </References> </Build> <Files> <Include> <File RelPath = "AmendStafferInfo.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "AmendStafferInfo.resx" DependentUpon = "AmendStafferInfo.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "AssemblyInfo.cs" SubType = "Code" BuildAction = "Compile" /> <File RelPath = "DataGridNoActiveCellColumn.cs" SubType = "Component" BuildAction = "Compile" /> <File RelPath = "DataGridNoActiveCellColumn.resx" DependentUpon = "DataGridNoActiveCellColumn.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "DataSet1.xsd" BuildAction = "Content" Generator = "MSDataSetGenerator" LastGenOutput = "DataSet1.cs" /> <File RelPath = "DataSet1.cs" DependentUpon = "DataSet1.xsd" SubType = "code" BuildAction = "Compile" DesignTime = "True" AutoGen = "True" /> <File RelPath = "DataSet1.xsx" DependentUpon = "DataSet1.xsd" BuildAction = "None" /> <File RelPath = "DataSet11.xsd" BuildAction = "Content" Generator = "MSDataSetGenerator" LastGenOutput = "DataSet11.cs" /> <File RelPath = "DataSet11.cs" DependentUpon = "DataSet11.xsd" SubType = "code" BuildAction = "Compile" DesignTime = "True" AutoGen = "True" /> <File RelPath = "DataSet2.xsd" BuildAction = "Content" Generator = "MSDataSetGenerator" LastGenOutput = "DataSet2.cs" /> <File RelPath = "DataSet2.cs" DependentUpon = "DataSet2.xsd" SubType = "code" BuildAction = "Compile" DesignTime = "True" AutoGen = "True" /> <File RelPath = "DataSet2.xsx" DependentUpon = "DataSet2.xsd" BuildAction = "None" /> <File RelPath = "HortationManage.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "HortationManage.resx" DependentUpon = "HortationManage.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "hrm.ico" BuildAction = "Content" /> <File RelPath = "IncomeTax.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "IncomeTax.resx" DependentUpon = "IncomeTax.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "LookupStafferInfo.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "LookupStafferInfo.resx" DependentUpon = "LookupStafferInfo.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "MainFrm.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "MainFrm.resx" DependentUpon = "MainFrm.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "OrgInfo.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "OrgInfo.resx" DependentUpon = "OrgInfo.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "PayoffHistory.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "PayoffHistory.resx" DependentUpon = "PayoffHistory.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "PunishmentManage.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "PunishmentManage.resx" DependentUpon = "PunishmentManage.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "SalaryManage.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "SalaryManage.resx" DependentUpon = "SalaryManage.cs" BuildAction = "EmbeddedResource" /> <File RelPath = "SetOrgInfo.cs" SubType = "Form" BuildAction = "Compile" /> <File RelPath = "SetOrgInfo.resx" DependentUpon = "SetOrgInfo.cs" BuildAction = "EmbeddedResource" /> </Include> </Files> </CSHARP> </VisualStudioProject>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值