FileVersionInfo.GetVersionInfo(typeof(Program).Assembly.CodeBase.Replace("file:///", "")).ProductVersion
That code, is a one-liner, but has quite a lot of steps for it to return the File Version. Is there maybe a AppDomain way to get the File Version? (Or in general a shorter way)
(The code DOES work - I dont know why its being voted to close as "Broken Code")
1 Answer 1
This one line might be left as-is, it's short and simple enough to be fully understood at first sight (especially if outer method has a meaningful name). However it has the disadvantage to be hard to test because it is doing 3 things:
- Getting the assembly which is the entry point of your application.
- Resolving the full path of that assembly.
- Reading the version information of that assembly.
In an ideal world I'd probably want to test them in isolation but it might happen only when I will have a bug there, for sure I want to reuse this code elsewhere then I will introduce separate methods.
public static FileVersionInfo GetProductVersion()
=> FileVersionInfo.GetVersionInfo(GetEntryAssemblyPath()).ProductVersion;
public static string GetEntryAssemblyPath()
=> GetAssemblyPath(Assembly.GetEntryAssembly());
I changed typeof(Program).Assembly
to Assembly.GetEntryAssembly()
because if I move it into a separate DLL then I won't have Program
. Note, however, that they may differ (see later).
What I'd really change is the way you obtain the path. Hard-coding file://
is tricky and unnecessary and you do not handle URL escapes. Moreover it will return an incorrect result if assembly has been loaded with Load()
. It's easier to write a slightly more correct version:
public static string GetAssemblyPath(Assembly assembly)
=> Uri.UnescapeDataString(new UriBuilder(assembly.Location).Path);
Note that here I'm accessing Assembly.Location
but it throws an exception for dynamically generated assembly and it's an empty string for assemblies loaded with Load()
. A "better" version should then handle both cases:
public static string GetAssemblyPath(Assembly assembly)
{
if (assembly.IsDynamic || assembly.Location == String.Empty)
return ""
return Uri.UnescapeDataString(new UriBuilder(assembly.Location).Path);
}
Little bit better but if we want to make GetEntryAssemblyPath()
a reusable method we should do better because the entry assembly is not always defined (web applications come to my mind in this moment but I kind-of remember about other issues in hosted environments). The choice between Assembly.CodeBase
and Assembly.Location
isn't important here but in another context you should check for Assembly.Location == String.Empty
also when using Assembly.CodeBase
.
In your tests you can now verify GetAssemblyPath()
in isolation and you may add separate tests for other functions when you will find an issue there (or when their code will be more complicate).
Last pedantic note: we're not checking if file effectively contains the required information and, usually, for assemblies it's also possible to read ProductInformationalVersionAttribute
(which might be different).
Location
instead ofCodeBase.Replace(...)
, and apparently there's a way to obtain it from an assembly attribute. \$\endgroup\$