我目前有一个应用程序显示构建号在其标题窗口。这很好,除了它对大多数用户没有任何意义,他们想知道他们是否有最新的版本-他们倾向于将其称为“上周四的”而不是1.0.8.4321版本。

计划是把构建日期放在那里,例如“App构建于21/10/2009”。

我正在努力寻找一种程序化的方法,将构建日期作为文本字符串提取出来,以便像这样使用。

对于版本号,我使用:

Assembly.GetExecutingAssembly().GetName().Version.ToString()

在定义了这些是怎么来的之后。

我希望编译日期(和时间,为了加分)也像这样。

非常感谢这里的指示(如果合适的话,借口双关语),或者更整洁的解决方案……


当前回答

这里没有讨论的选项是将您自己的数据插入到AssemblyInfo.cs中,“AssemblyInformationalVersion”字段似乎是合适的——我们有几个项目,我们在其中做了类似的构建步骤(然而我对这种工作方式并不完全满意,所以真的不想重现我们已经得到的东西)。

在codeproject上有一篇关于这个主题的文章:http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

其他回答

您可以在构建过程中启动一个额外的步骤,将日期戳写入文件,然后显示该文件。

在项目属性选项卡上,查看构建事件选项卡。有一个选项可以执行构建前或构建后命令。

对于。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();

将以下内容添加到预构建事件命令行:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

添加这个文件作为资源,现在你有'BuildDate'字符串在你的资源。

在将文件插入资源(作为公共文本文件)后,我通过

string strCompTime = Properties.Resources.BuildDate;

若要创建资源,请参见如何在. net中创建和使用资源。

对于任何需要在Windows 8 / Windows Phone 8中获得编译时间的人:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
    {
        var pkg = Windows.ApplicationModel.Package.Current;
        if (null == pkg)
        {
            return null;
        }

        var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
        if (null == assemblyFile)
        {
            return null;
        }

        using (var stream = await assemblyFile.OpenSequentialReadAsync())
        {
            using (var reader = new DataReader(stream))
            {
                const int PeHeaderOffset = 60;
                const int LinkerTimestampOffset = 8;

                //read first 2048 bytes from the assembly file.
                byte[] b = new byte[2048];
                await reader.LoadAsync((uint)b.Length);
                reader.ReadBytes(b);
                reader.DetachStream();

                //get the pe header offset
                int i = System.BitConverter.ToInt32(b, PeHeaderOffset);

                //read the linker timestamp from the PE header
                int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);

                var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
                return dt.AddSeconds(secondsSince1970);
            }
        }
    }

对于任何需要在Windows Phone 7中获得编译时间的人:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
    {
        const int PeHeaderOffset = 60;
        const int LinkerTimestampOffset = 8;            
        byte[] b = new byte[2048];

        try
        {
            var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
            using (var s = rs.Stream)
            {
                var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
                int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
            }
        }
        catch (System.IO.IOException)
        {
            return null;
        }

        int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
        int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
        var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
        dt = dt.AddSeconds(secondsSince1970);
        return dt;
    }

注意:在所有情况下,你都运行在沙箱中,所以你只能获得你部署应用程序的程序集的编译时间。(也就是说,这对GAC中的任何东西都无效)。

关于从程序集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.