我目前有一个应用程序显示构建号在其标题窗口。这很好,除了它对大多数用户没有任何意义,他们想知道他们是否有最新的版本-他们倾向于将其称为“上周四的”而不是1.0.8.4321版本。
计划是把构建日期放在那里,例如“App构建于21/10/2009”。
我正在努力寻找一种程序化的方法,将构建日期作为文本字符串提取出来,以便像这样使用。
对于版本号,我使用:
Assembly.GetExecutingAssembly().GetName().Version.ToString()
在定义了这些是怎么来的之后。
我希望编译日期(和时间,为了加分)也像这样。
非常感谢这里的指示(如果合适的话,借口双关语),或者更整洁的解决方案……
对于. net Core项目,我修改了Postlagerkarte的答案,用构建日期更新程序集版权字段。
直接编辑csproj
以下内容可以直接添加到csproj中的第一个PropertyGroup:
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
替代方案:Visual Studio项目属性
或者直接将内部表达式粘贴到Visual Studio中项目属性Package部分的Copyright字段中:
Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))
这可能有点令人困惑,因为Visual Studio将计算表达式并在窗口中显示当前值,但它也将在幕后适当地更新项目文件。
通过Directory.Build.props实现解决方案范围
您可以将上面的<Copyright>元素放入解决方案根目录下的directory . build .props文件中,并将其自动应用于该目录中的所有项目,假设每个项目不提供自己的Copyright值。
<Project>
<PropertyGroup>
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
</PropertyGroup>
</Project>
props:自定义构建
输出
示例表达式将为您提供如下版权:
Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)
检索
你可以在Windows中从文件属性中查看版权信息,或者在运行时获取它:
var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);
Console.WriteLine(version.LegalCopyright);
Jeff Atwood在《艰难地确定构建日期》中谈到了这个问题。
最可靠的方法是从可执行文件中嵌入的PE头中检索链接器时间戳——一些c#代码(由Joe Spivey编写)来自Jeff文章的注释:
public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
var filePath = assembly.Location;
const int c_PeHeaderOffset = 60;
const int c_LinkerTimestampOffset = 8;
var buffer = new byte[2048];
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
stream.Read(buffer, 0, 2048);
var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
var tz = target ?? TimeZoneInfo.Local;
var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);
return localTime;
}
使用的例子:
var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();
注意:这个方法适用于。net Core 1.0,但在。net Core 1.1之后就不再适用了——它给出的年份是1900-2020年之间的随机值。
对于. net Core(。NET 5+),可以这样做。它的优点在于不需要添加或嵌入文件,没有T4,也没有预构建脚本。
在你的项目中添加这样一个类:
namespace SuperDuper
{
[AttributeUsage(AttributeTargets.Assembly)]
public class BuildDateTimeAttribute : Attribute
{
public string Date { get; set; }
public BuildDateTimeAttribute(string date)
{
Date = date;
}
}
}
更新项目的.csproj以包含如下内容:
<ItemGroup>
<AssemblyAttribute Include="SuperDuper.BuildDateTime">
<_Parameter1>$([System.DateTime]::Now.ToString("s"))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
注意,_Parameter1是一个神奇的名字——它意味着BuildDateTime属性类的构造函数的第一个(也是唯一一个)参数。
这就是在程序集中记录构建日期时间所需的全部内容。
然后读取程序集的构建日期时间,执行如下操作:
private static DateTime? getAssemblyBuildDateTime()
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var attr = Attribute.GetCustomAttribute(assembly, typeof(BuildDateTimeAttribute)) as BuildDateTimeAttribute;
if (DateTime.TryParse(attr?.Date, out DateTime dt))
return dt;
else
return null;
}
注1(根据评论中的Flydog57):如果你的.csproj有属性GenerateAssemblyInfo在其中列出并设置为false,构建将不会生成程序集信息,你将不会在程序集中获得BuildDateTime信息。因此,要么不要在.csproj中提到GenerateAssemblyInfo(这是新项目的默认行为,如果没有特别设置为false, GenerateAssemblyInfo默认为true),要么显式地将其设置为true。
注2(根据Teddy在评论中的说法):在给出的_Parameter1示例中,我们使用::Now来使用DateTime。现在,这是您的计算机上的本地日期和时间,如果适用日光节约时间和您的本地时区。如果你想使用::UtcNow来使用DateTime,你可以这样做。UtcNow以便将构建日期和时间记录为UTC/GMT。
对于。net 5,我已经成功地使用了这种方法。(在这里找到)。
把这个添加到.csproj文件中:
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
获取构建日期的方法:
private static DateTime GetBuildDate(Assembly assembly)
{
const string BuildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix);
if (index > 0)
{
value = value.Substring(index + BuildVersionMetadataPrefix.Length);
if (DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
{
return result;
}
}
}
return default;
}
用法:
var buildTime = GetBuildDate(Assembly.GetExecutingAssembly());
buildTime = buildTime.ToLocalTime();
关于从程序集PE头的字节中提取构建日期/版本信息的技术,Microsoft已经从Visual Studio 15.4开始更改了默认的构建参数。新的默认包含确定性编译,这使得有效的时间戳和自动递增的版本号成为过去式。时间戳字段仍然存在,但它被一个永久值填充,该值是某个东西的哈希值,而不是任何构建时间的指示。
这里有一些详细的背景
对于那些将有用的时间戳优先于确定性编译的人来说,有一种方法可以覆盖新的默认值。您可以在感兴趣的程序集的.csproj文件中包含一个标记,如下所示:
<PropertyGroup>
...
<Deterministic>false</Deterministic>
</PropertyGroup>
Update:
I endorse the T4 text template solution described in another answer here. I used it to solve my issue cleanly without losing the benefit of deterministic compilation. One caution about it is that Visual Studio only runs the T4 compiler when the .tt file is saved, not at build time. This can be awkward if you exclude the .cs result from source control (since you expect it to be generated) and another developer checks out the code. Without resaving, they won't have the .cs file. There is a package on nuget (I think called AutoT4) that makes T4 compilation part of every build. I have not yet confronted the solution to this during production deployment, but I expect something similar to make it right.