When to Change File/Assembly Versions


First of all, file versions and assembly versions need not coincide with each other. I recommend that file versions change with each build. But, don’t change assembly versions with each build just so that you can tell the difference between two versions of the same file; use the file version for that. Deciding when to change assembly versions takes some discussion of the types of builds to consider: shipping and non-shipping.


Non-Shipping Builds
In general, I recommend keeping non-shipping assembly versions the same between shipping builds. This avoids strongly-named assembly loading problems due to version mismatches. Some people prefer using publisher policy to redirect new assembly versions for each build. I recommend against that for non-shipping builds, however: it doesn’t avoid all of the loading problems. For example, if a partner x-copies your app, they may not know to install publisher policy. Then, your app will be broken for them, even though it works just fine on your machine.


But, if there are cases where different applications on the same machine need to bind to different versions of your assembly, I recommend giving those builds different assembly versions so that the correct one for each app can be used without having to use LoadFrom/etc.


Shipping Builds
As for whether it’s a good idea to change that version for shipping builds, it depends on how you want the binding to work for end-users. Do you want these builds to be side-by-side or in-place? Are there many changes between the two builds? Are they going to break some customers? Do you care that it breaks them (or do you want to force users to use your important updates)? If yes, you should consider incrementing the assembly version. But, then again, consider that doing that too many times can litter the user’s disk with outdated assemblies.


When You Change Your Assembly Versions
To change hardcoded versions to the new one, I recommend setting a variable to the version in a header file and replacing the hardcoding in sources with the variable. Then, run a pre-processor during the build to put in the correct version. I recommend changing versions right after shipping, not right before, so that there’s more time to catch bugs due to the change.

Comments (28)

  1. Rick Byers says:

    Thanks for the great information Suzanne!

    In the past, I’ve spent a lot of time dealing with this question. We used to keep our assembly version numbes fxied for non-shipping builds, but we had a lot of problems during development and testing with binding to old copies of assemblies when we expected to get updated ones. We quickly decided that using automatic version numbers (eg. ‘1.0.*") was what we should be doing, and so didn’t examine the problem in great detail. However, it appeared that (possibly under certain circumstances) assemblies in the shadow copy cache ("Local SettingsApplication DataAssembly") were only updated if the version changed. On many occasions we’d get some strange behaviour (eg. MissingMethodException), then go clear the shadow copy cache (and the IE cache and download cache for good measure), which would correct the problem. There were also some cases where we thought VisualStudio was using the assembly name as its identity, and eg. not doing the reference copy-local if the name hadn’t changed.

    Are you aware of any issues like theese? Perhaps the problems we experienced then have been corrected in .NET 1.1 and VS.NET 2003. Based on this behaviour, on the default VS.Net Assembly Info files, and on Don Box’s discussion of this issue in Essential .NET (pg 46), we came to the conclusion that the official ‘best practice’ reccomendation from Microsoft was to allways increment your assembly version numbers on each build. However, doing this has also been a pain, especially since VS.Net will sometimes try to rebuild some projects when I start debugging even when I haven’t modified any source. We are also doing our best to avoid using the GAC (so we can support xcopy installs), so as you mention, publisher policy files aren’t an ideal solution for us.

    Needless to say, I was very interested to read you oppinion on this matter. Can you provide any more information about this issue? I’d be particularily interested to hear what Microsoft and others are doing for versioning with large multi-assembly applications.

  2. Suzanne says:

    Yes, strongly-named assemblies already in the download cache will not be downloaded again if the assembly identity hasn’t changed. To deal with this, our test scripts delete the download cache at the beginning of the test (gacutil /cdl). We find it preferable to make a one-time change to do that in scripts than to deal with the constant pains of changing versions often.

  3. Rick Byers says:

    Ok, that makes sense and is easy enough to deal with. But I’m pretty sure we experienced problems even when all assemblies are loaded locally (and so the download cache hadn’t been used), and that clearing the shadow copy cache resolved the problem.

    Are you aware of similar identity issues with the shadow copy cache?

  4. Suzanne says:

    Yes, they’re basically the same thing from Fusion’s perspective.

  5. Magnus Hiie says:

    Thanks for your great insight on Fusion!

    We have a multitier web-based solution with many modules, which consist of many assemblies. We have a common solution-independent framework, a solution framework, and module-specific code for each module. We want to support side-by-side execution for the solution-independent framework and the solution framework, but at the same time, we don’t want to use GAC because:
    – we want to support XCOPY deployment (together with automatic appdomain reload when assemblies change)
    – the frameworks are large, and we don’t want to put them on the system partition together with operating system files
    – if an assembly is installed in the GAC, we cannot use external files accompanying the assembly (right now we use the parent directory of Assembly.CodeBase to locate the files)
    – if an assembly is installed in the GAC (say, an interface library for the framework), all dependent assemblies also have to be installed in GAC

    Right now we have a library similar to Fusion that we call "Private Assembly Cache" that binds to AppDomain.AssemblyResolve event that locates assemblies from the framework directories, using directory structure that supports versioning (similar to GAC directory structure). But it’s quite complex and it seems it incurs quite a large a performance hit.

    Do you have any general suggestion how to deploy frameworks that are used by multiple applications, but avoid the problems with GAC described above?

    Some specific questions:
    – Is it possible to register assemblies in the GAC, but physically store them in some framework directory?
    – Am I right when I’m saying that private assemblies and side-by-side execution are mutually exclusive (probing does not support multiple versions)?
    – What’s the best way for an assembly in the GAC to locate it’s accompanying files?
    – Are there any plans to support XCOPY deployment to GAC?

    Best regards and thanks again,
    Magnus

  6. Suzanne says:

    Answers to your specific questions below, and they include the answer to your overall question:

    (1) No, GAC assemblies are required to be in the GAC location.

    (2) You can make it work. First, make sure that the files in the different directories each have unique binding identities (see http://blogs.msdn.com/suzcook/archive/2003/07/21/57232.aspx ). Then, if you want all these dirs probed, (and you have control of the AppDomain(s) using these assemblies,) use their common parent dir as the AppDomainSetup.ApplicationBase, and the list of subdirs as the PrivateBinPath. But, probing the dirs won’t help for multiple files of the same name, but in different dirs. You will need to have a config file with one codebase redirect for each of them, or else you’ll get a FileLoadException. That will avoid probing, but causes a new FileIOPermission.Read and PathDiscovery demand on the given path.

    (3) Ideally, they’d be in the GAC. But, besides that, if you have control over the calling code, you could have it pass you the outside dir. If you don’t have that control, you could set the dir in the registry.

    (4) I believe no.

  7. Michael Casey says:

    Suzzane,

    I am very pleased to have found your Blog on this subject. I think that the subject of best practices for version management deserves a lot more attention by Microsoft. Versioning practices in general are very subjective, but the subtle impact of versioning within .NET assemblies definitely removes some of that subjectivity. For the benefit of others, one of the best articles that I have found that touches on these types of issues is titled "Team Development with Visual Studio.NET and Visual Source Safe."
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/tdlg_rm.asp

    This is a relatively comprehensive article that bridges the gap between Visual Studio Project management practices and source control version management practices. It is impossible to manage a real world product release process without these types of insights.

    The reason that I’m writing is that it took me a long time to discover how to manipulate the various Assembly and File properties related to versioning. My approach is partially based upon the information contained in the previously mentioned MSDN article, and partially derived through experimentation. I won’t go into the whole deployment strategy thing, which may be worth discussing in one of your other threads, but I wanted to specifically mention the treatment of version numbers.

    The sparse documentation that is available on version numbering practices leaves one trying to sort out the proper version numbering strategy. What is especially difficult is trying to reconcile the default behavior of Visual Studio.NET projects that auto-generate the Build/Revision, versus the vague version numbering Microsoft reccommendations that utilize all 4 parts of the version number:

    MAJOR: Assemblies with the same name but different major versions are not interchangeable. This would be appropriate, for example, for a major rewrite of a product where backward compatibility cannot be assumed.
    MINOR: If the name and major number on two assemblies are the same, but the minor number is different, this indicates significant enhancement with the intention of backward compatibility. This would be appropriate, for example, on a point release of a product or a fully backward compatible new version of a product.
    BUILD: A difference in build number represents a recompilation of the same source. This would be appropriate because of processor, platform, or compiler changes.
    REVISION: Assemblies with the same name, major, and minor version numbers but different revisions are intended to be fully interchangeable. This would be appropriate to fix a security hole in a previously released assembly.

    The issue becomes further complicated when one wants to introduce text based information that describes the version (e.g. Beta, RC1, etc.) for a special build.

    What I discovered is that the Windows File system treats the File Version number as the primary version number (i.e. the version number shown when examining the properties of a file in Windows Explorer). In contrast, the Assembly Version is shown as a secondary property, but it is critical to the resolution of strong-named assembly references. As a result, I came to a similar conclusion as you, but my plan of action was inverted; that is, I was planning on using the Auto-Generated Build/Revision number for the Assembly Version, and using the manually modified Build/Revision numbers for the File Version number. My reasoning for this was as follows:

    1) It is the default behavior of Visual Studio to build AssemblyInfo.cs files that include the wild-carded Assembly Version number. The File Version Number does take on the same value as the AssemblyVersion when not specified, but it is not explicitly included (by default). When in doubt, I usually try not to violate the default behavior of anything; presumably, someone establishes defualt behavior using some form of reasoning.

    2) The AssemblyVersion number must be a 4 part numeric value, whereas the File Version can be a string. If you take the approach of auto-generating the File Version, the ability to include other build specific strings does not make as much sense. In contrast, if the Assembly Version is the auto-generated number, the manual process of changing the File Version coincides with the manual process of identifying version numbers for release, which may be strictly numeric (or include additional strings).

    3) If the File Version is the primary version number (viewable by users of the application), it makes more sense to me that this number should be sequentially updated. In other words, I think users might not appreciate viwing the large auto-generated build/revision numbers. I realize that there is yet another version number that can be utilized for this purpose (i.e. Product Version number), but this is still not the primary version attribute of the file.

    With that said, we actually don’t use Visual Studio to auto-generate the Assembly Version number. Since our application is a multi-solution system, we found it especially troublesome to have the AssemblyVersion numbers constantly changing on each developer’s machine. In the interest of keeping the Assembly Version number stable on a given developer’s machine; our Assembly Version numbers are actually hard-coded into the AssemblyInfo.cs file. However, this number is automatically generated (using the same algorithm provided by VS) and replaced during our scripted system builds, such that every build available for testing does contain a unique Aseembly Version. On the other hand, developers may rebuild as often as they like locally, without worry about changing Assembly Versions.

    So, in the end my Assembly Version number may be changing between shipping builds, but the number does not change for a given snapshot of the source code. I think this partially addresses your concern about keeping the Assembly Version number stable. You indicate that this may cause problems with version mismatches (of presumably otherwise compatible assemblies), but I have yet to determine how to definitely measure compatibility. Given the nature of team development, I’m inclinded to think that it’s better to assume incompatibility from one system build to the next. The down fall of this approach will likely be deployment of too many copies of the same libraries (loading redundant assemblies in memory), but my expectation is that we could limit the number of partial system releases that occurr between full system releases.

    If you have any other insights into these practices for version numbering, I would really like to read more. In addition, I would be interested to read about any suggestions on how to perform impact analysis of coding changes, to help determine when version numbers should be changed to indicate incompatibility.

  8. Gans says:

    Good stuff this.

    One piece of this puzzle is the approach to shipping patches to multi-solution applications.
    Assuming version numbers are already controlled, one could ship patches along with publisher policy files as required. This is simple enough to automate and send.

    The problem is w.r.t. debugging applications for which we have redirected assemblies. VS.Net doesn’t seem to like this. In cases where a solutions has dependencies to a redirected assembly through other dependencies, VS.Net does not resolve conflict (doesn’t use the ppf during compile). (E.g. A refs B, C. B refers to Dv1, C refers to Dv2. D v1 is redirected to v2 with PPF. The problem is while we compile/debug A)

    This also makes debugging applications that refer to redirected assemblies rather difficult.

    What would be a good approach to this?
    Should one have the dev versions completely separate from ship versions? While this would solve the problem, wouldn’t it break the idea of having close-to-prod code equivalent in dev (prod support)?

  9. Suzanne says:

    VS.NET loads user assemblies by path, and policy is not applied today for those loads. The workaround is to give the path to the post-policy assembly, instead of the pre-policy one.

    For your other question about dev. vs. prod. versions, see my original post for a discussion about shipping and non-shipping builds.

  10. Rajkumar says:

    I have a doubt with multiple versions of same assemblies in GAC.How do i access both??

    For example, if i have a assembly A with a method say func1 and i put it in GAC.

    No i build a new version of the same assembly and put it in GAC with a new method func2.

    If one of my application wants funct1 of Ver1

    and func2 of Ver2 how do i go about this??

    If i add as a reference to the assembly with VS .NET it points me to Ver2.

    How do i access the Ver1.

    Thanks in advance.

  11. Suzanne says:

    First, make sure that the two versions have different assembly identities. See http://blogs.msdn.com/suzcook/archive/2003/07/21/57232.aspx . Probably, this means that you will want to give them different assembly versions and strongly name them.

    Second, if you want a static reference, you will need to use a compiler that supports it. (I don’t believe any of the pre-v2.0 VS.NET compilers did.) The CLR has always supported that, so once it’s been compiled with the references you want, it will just work at runtime.

    You can also do this dynamically, to avoid the need for compiler support. For example, you could get the two Assembly objects using Assembly.Load() with the appropriate display names, and invoke the appropriate methods.

  12. shikarishambu says:

    Makes sense. But, how do I update the FileVersion info for my .NET project/assembly?

  13. Simon Burgess says:

    Regarding shipping and non-shipping builds – I work on a web portal product for which we have one production environment out there on the www and a few other internal testing environments. In our source control, we have a branch which represents each one of these testing environments including live – we build on each of the branches to produce a deployment to that environment only i.e. builds on the Dev branch are only ever deployed to the Dev environment, builds on the Live branch are only ever deployed to the Live environment etc. Furthermore, every build is always then deployed to that environment. I am trying to apply your shipping analogy to my situation. Either the only production/shipping builds occur on the Live environment branch, therefore I change the assemblyversion for everybuild on this branch and every build on any of the testing branches is a non-shipping build therefore the assemblyversion never changes; or every build on every environment is a shipping build therefore always change the assemblyversion. I would welcome any feedback you have on this, I was trying to avoid having the same assemblyversion numbers across different environments but presumably I could use the fileversion to distinguish between environments with a ‘dev_’ or ‘live_’ prefix for example

  14. Jovo Filips says:

    Great conversation everyone, thanks. At our shop, we created a special build program that builds releases for our customers (and development builds). The QA manager just puts in a release version number and presses a button and our collection of projects and solutions representing our product and deployment items is built (I’ll need to readup on vs2005 team server to see if I can ditch my custom program). For the idea of the "invoke a preprocessor" to change version numbers… well that is exactly what we do. It is pretty much boilerplate code, but I’m happy to share a couple methods (C#) that I have extracted for you fine people to use in your own preprocessors if you like — no warranties mind you, just trying to be helpful. (I have no idea how this blog may reformat this code, so apologies to all if it is messed up)

    Method to change AssemblyVersion :

    private void FixupAssemblyVersion(string FileName, bool Force, string NewProductVersion)

    {

    string str;

    using (StreamReader fp = new StreamReader(FileName))

    {

    str = fp.ReadToEnd();

    }

    string pattern;

    if (Force)

    pattern = @"AssemblyVersion(""[^""]*"")";

    else

    pattern = @"AssemblyVersion(""[^""]**"")"; // last part of Version must be "*" unless I’m forcing the new version in

    string str1 = Regex.Replace(str, pattern , @"AssemblyVersion(""" + NewProductVersion + "")", RegexOptions.Singleline);

    if (str1 != str)

    {

    using (StreamWriter fp = new StreamWriter(FileName))

    {

    fp.Write(str1);

    }

    }

    }

    Probably more interesting method for everyone else, this will update the version number in a setup deployment project, as well as generate new product and package code guids.

    private void FixupSetupProjectVersion(string FileName, string NewProductVersion)

    {

    // Setup projects can have a 3-part version (not a 4-part version)

    StringBuilder sb = new StringBuilder(300000);

    using (StreamReader fp = new StreamReader(FileName))

    {

    string ProductVersionPattern = @"""ProductVersion"" = ""8:";

    string ProductCodePattern = @"""ProductCode"" = ""8:{";

    string PackageCodePattern = @"""PackageCode"" = ""8:{";

    string NewProductCode = System.Guid.NewGuid().ToString("").ToUpper();

    string NewPackageCode = System.Guid.NewGuid().ToString("").ToUpper();

    while (true)

    {

    string s = fp.ReadLine();

    if (s == null) break;

    if (s.IndexOf(ProductVersionPattern) >= 0)

    {

    int i1 = s.IndexOf(ProductVersionPattern);

    i1 += ProductVersionPattern.Length;

    int j1 = s.IndexOf(""", i1);

    string s1 = s.Substring(0, i1) + NewProductVersion + s.Substring(j1, s.Length – j1);

    sb.Append(s1 + "rn");

    }

    else

    if (s.IndexOf(ProductCodePattern) >= 0)

    {

    int i1 = s.IndexOf(ProductCodePattern);

    i1 += ProductCodePattern.Length;

    int j1 = s.IndexOf("}", i1);

    string s1 = s.Substring(0, i1) + NewProductCode + s.Substring(j1, s.Length – j1);

    sb.Append(s1 + "rn");

    }

    else

    if (s.IndexOf(PackageCodePattern) >= 0)

    {

    int i1 = s.IndexOf(PackageCodePattern);

    i1 += PackageCodePattern.Length;

    int j1 = s.IndexOf("}", i1);

    string s1 = s.Substring(0, i1) + NewPackageCode + s.Substring(j1, s.Length – j1);

    sb.Append(s1 + "rn");

    }

    else

    {

    sb.Append(s + "rn");

    }

    }

    }

    string str1 = sb.ToString();

    using (StreamWriter fp = new StreamWriter(FileName))

    {

    fp.Write(str1);

    }

    }

  15. Mani SP says:

    This is very informative.

    BTW, if I have a .NET module loaded into my GAC and if I need to get its File and assembly Versions, how to go about it using a simple query program?

  16. J. Blumenauer says:

    This Blog contains some excellent information regarding versioning.  However, I have a question regarding versioning as it relates to internally developed .NET plug-ins.  Is there a standard practice for versioning plug-ins as they relate to the application(s) that they will ultimately be plugged into?  If I have a framework application product that contains essential or basic functionality, and then I develop plug-ins that may be added at the user’s discretion, should the plug-ins be versioned independent of the framework product or should they be aligned with the product version?  I’m thinking they should be independent since they may also be used with another product?  

  17. Abhishek says:

    I have a strong named, signed assembly say abc.dll which I have deployed in GAC during product deployment.

    This assembly is being dynamically loaded using Assembly.Load method in different parts of the application and in different processes. I use the assembly’s fully qualified name to load it.

    Now, this assembly will be updated as part of a service pack or a sandbox. By updating I mean replacement of the existing assembly.

    I want to know how the versioning of these assemblies should be done? Also, if the assembly versions (revision numbers) are changed in service packs, do I need to read the version number of assembly from some config file or will the same code, with the original version number will do?

    E.g.

    Initial Deployment

    abc.dll Version 1.0.0.1

    Service Pack

    abc.dll Version 1.0.0.2

    will the code

    Assembly.Load(“abc, Version=1.0.0.1, …”);

    work?

    or do I need to use?

    Version ver = /*Read from Config File */

    Assembly.Load(“abc, Version=<ver>, …”);

    Thanks

  18. I hesitate to talk about this because I don’t want people who don’t know about it to think, "Hey, what’s

  19. So, after checking out the binding context options , you’ve decided to switch your app to use the Load

  20. avijit6399 says:

    Any latest Aseembly Versioning concept in .Net 2.0 version?

  21. I have found this link very rich about information need to me with code examples

    so God give you more courge that you work for the peoples

    Rgards

    Inaam Subhani

  22. Ben says:

    Hi Suzanne,

    <br><br>

    I include a link to a shared assemblyinfo.vb file for central versioning, but find that when I update the version – often the assembly version isn’t updated in the compiled assembly. In other words, it stays the same when it should have changed.

    <br><br>

    I have taken to changing the version to a radically different one than the one I need (which may contain only a small increment), rebuilding – sometimes twice before the change is recognized, then changing to the version I need and rebuilding again.

    <br><br>

    This works, but is a flakey and time consuming work around when rebuilding a lot of projects.

    <br><br>

    Do you know why this happens and how to force each project to update to a specified assembly version (from a linked assemblyinfo.vb file) with a single rebuild?

    <br><br>

    yours curiously,

    <br><br>

    Ben

  23. Donald says:

    When I look in the GAC I see MS naming assemblies with the verison number as part of the name. Examples include:

    Microsoft.Office.Tools.Excel.v9.0

    Microsoft.VisualStudio.Shell.Interop.8.0

    Microsoft.VisualStudio.Shell.Interop.9.0

    Microsoft.Build.Utilities.v3.5

    Can you please comment on this.