⬆️ ⬇️

Synchronizing AssemblyVersion and Publish Version in a ClickOnce application

Good day.



Made a ClickOnce application. All is well, but tiresome to update the version number. The fact is that when uploading an update, you need to change the version in both AssemblyInfo and csproj. So I did:



public static class VersionInfo { public const string VersionString = "1.0.3"; } 


And in AssemblyInfo we refer to this property:

')

 [assembly: AssemblyVersion(VersionInfo.VersionString)] [assembly: AssemblyFileVersion(VersionInfo.VersionString)] 


Then you need to get into the project properties, select the Publish tab and change the Publish Version.



image



Or manually edit the csproj project file:



 <ApplicationVersion>1.0.3.%2a</ApplicationVersion> 


As I wrote above, it is tiring, especially morally. I wanted to automate this process so that you can change the version in only one place. Then click on the menu Build -> Publish, and the version in the rest of the places will update itself. It seemed to me convenient to change the value of the VersionInfo.VersionString property, after which, before compiling, the fresh value should be snapped into the project file. Surely it can be different, but I think the solutions will be similar to mine.



So, before compiling, you need to take a value from the VersionInfo class and put it in the project file. Such frauds should seem to be able to do fody, but I have not found an example of how it can work with project files. Therefore made through MSBuild Task. The task of the task is simple - before compiling, find the file with the VersionInfo class, then pull out the version from there, find the project file, put the new version in there. Along the way, catch errors and notify the user about them in the building output. This is the code that came out (reference nuget package "Microsoft.Build.Tasks.Core"):



 public class PublishVersionSyncTask : Task { [Required] public string ProjectFilePath { get; set; } [Required] public string VersionStringFilePath { get; set; } [Output] public string Error { get { return this._error; } set { this._error = value; } } string _error; public override bool Execute() { if(!File.Exists(ProjectFilePath)) { Error = $"Project File \"{ProjectFilePath}\" does not exists"; return true; } if(!File.Exists(VersionStringFilePath)) { Error = $"Version File \"{VersionStringFilePath}\" does not exists"; return true; } string versionString = null; var allCodeLines = File.ReadAllLines(VersionStringFilePath); foreach(var codeLine in allCodeLines) { if(codeLine.Contains("VersionString")) { versionString = codeLine.Split('"').Where(s => s.Contains('.')).FirstOrDefault(); break; } } if(String.IsNullOrEmpty(versionString)) { Error = "Can not find version string."; return true; } if(versionString.Split('.').Length != 3) { Error = $"Version string has wrong format: {versionString}. It must be xyz"; return true; } allCodeLines = File.ReadAllLines(ProjectFilePath); List<string> fixedCodeLines = new List<string>(); foreach(var codeLine in allCodeLines) { if(!codeLine.Contains("<ApplicationVersion>")) { fixedCodeLines.Add(codeLine); continue; } if(codeLine.Contains(versionString)) return true; fixedCodeLines.Add($" <ApplicationVersion>{versionString}.%2a</ApplicationVersion>"); } try { if(File.Exists(ProjectFilePath + ".bak")) File.Delete(ProjectFilePath + ".bak"); } catch { Error = $"Can not delete {ProjectFilePath}.bak"; return true; } File.Copy(ProjectFilePath, ProjectFilePath + ".bak"); try { File.Delete(ProjectFilePath); } catch { File.Delete(ProjectFilePath + ".bak"); Error = $"Can not delete {ProjectFilePath}"; return true; } File.WriteAllLines(ProjectFilePath, fixedCodeLines); File.Delete(ProjectFilePath + ".bak"); return true; } } 


Roslyn can work with project files “humanly”, but it will work longer. And it will be executed before each compilation (although you can make it so that in the Debug configuration this code does not run, but I do not need it).



We compile, we enclose libraries somewhere near the folder of the target project. In the target project, create a PublishVersionSynchronizer.targets file with the following content:



 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask TaskName="PublishVersionSynchronizer.PublishVersionSyncTask" AssemblyFile="$(TargetDir)\..\..\lib\PublishVersionSynchronizer\PublishVersionSynchronizer.dll"/> <PropertyGroup> <BuildDependsOn> PublishVersionSync; BeforeBuild; CoreBuild; AfterBuild </BuildDependsOn> </PropertyGroup> <Target Name="PublishVersionSync"> <PublishVersionSyncTask ProjectFilePath="$(MSBuildProjectFullPath)" VersionStringFilePath="$(MSBuildProjectDirectory)\Config\VersionInfo.cs"> <Output PropertyName="ErrorMessage" TaskParameter="Error" /> </PublishVersionSyncTask> <Message Text="(out) Publish version patched" Condition="'$(ErrorMessage)' == ''"/> <Error Condition="'$(ErrorMessage)' != ''" Text="$(ErrorMessage)" /> </Target> </Project> 


Make this file BuildAction = “Content”, open the project file, append import of this file:



 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /><!--  --> <Import Project="PublishVersionSynchronizer.targets" /> 


And it works.



If someone needs source code, they are on a githaba .



Thank.

Source: https://habr.com/ru/post/345390/



All Articles