Добрый день.
Сделал приложение ClickOnce. Всё хорошо, но утомляет обновлять номер версии. Дело в том, что при выкладывании обновления нужно менять версию как в AssemblyInfo, так и в csproj. Вот так я сделал:
А в AssemblyInfo на это свойство ссылаемся:
Затем нужно залезть в свойства проекта, выбрать вкладку Publish и поменять Publish Version.

Либо руками править файл проекта csproj:
Как я уже написал выше, это утомляет, особенно морально. Захотелось автоматизировать этот процесс, чтобы можно было поменять версию только в одном месте. После чего нажать в меню Build -> Publish, и версия в остальных местах сама обновится. Мне показалось удобным менять значение свойства VersionInfo.VersionString, после чего перед компиляцией свежее значение должно прокинуться в файл проекта. Наверняка можно и по другому, но думаю, варианты решения будут похожи на мой.
Итак, нужно перед компиляцией взять значение из класса VersionInfo и положить его в файл проекта. Подобные махинации вроде должен уметь делать fody, но я не нашёл примера, как он может работать с файлами проектов. Поэтому сделал через MSBuild Task. Задача таски проста — перед компиляцией найти файл с классом VersionInfo, затем вытащить оттуда версию, найти файл проекта, засунуть туда новую версию. По пути поймать ошибки и известить пользователя о них в build output. Вот такой код получился (референсил nuget пакет «Microsoft.Build.Tasks.Core»):
Roslyn умеет работать с файлами проекта «по человечески», но это будет дольше работать. А выполняться будет перед каждой компиляцией (хотя можно сделать так, чтобы в Debug конфигурации этот код не гонялся, но мне это не нужно).
Компилируем, подкладываем библиотеки где-то рядом с папкой целевого проекта. В целевом проекте создаём файл PublishVersionSynchronizer.targets с таким контентом:
Делаем этому файлу BuildAction=«Content», открываем файл проекта, дописываем импорт этого файла:
И всё работает.
Если кому-то нужны исходники, они на гитхабе.
Спасибо.
Сделал приложение ClickOnce. Всё хорошо, но утомляет обновлять номер версии. Дело в том, что при выкладывании обновления нужно менять версию как в AssemblyInfo, так и в csproj. Вот так я сделал:
public static class VersionInfo { public const string VersionString = "1.0.3"; }
А в AssemblyInfo на это свойство ссылаемся:
[assembly: AssemblyVersion(VersionInfo.VersionString)] [assembly: AssemblyFileVersion(VersionInfo.VersionString)]
Затем нужно залезть в свойства проекта, выбрать вкладку Publish и поменять Publish Version.

Либо руками править файл проекта csproj:
<ApplicationVersion>1.0.3.%2a</ApplicationVersion>
Как я уже написал выше, это утомляет, особенно морально. Захотелось автоматизировать этот процесс, чтобы можно было поменять версию только в одном месте. После чего нажать в меню Build -> Publish, и версия в остальных местах сама обновится. Мне показалось удобным менять значение свойства VersionInfo.VersionString, после чего перед компиляцией свежее значение должно прокинуться в файл проекта. Наверняка можно и по другому, но думаю, варианты решения будут похожи на мой.
Итак, нужно перед компиляцией взять значение из класса VersionInfo и положить его в файл проекта. Подобные махинации вроде должен уметь делать fody, но я не нашёл примера, как он может работать с файлами проектов. Поэтому сделал через MSBuild Task. Задача таски проста — перед компиляцией найти файл с классом VersionInfo, затем вытащить оттуда версию, найти файл проекта, засунуть туда новую версию. По пути поймать ошибки и известить пользователя о них в build output. Вот такой код получился (референсил nuget пакет «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 x.y.z"; 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 умеет работать с файлами проекта «по человечески», но это будет дольше работать. А выполняться будет перед каждой компиляцией (хотя можно сделать так, чтобы в Debug конфигурации этот код не гонялся, но мне это не нужно).
Компилируем, подкладываем библиотеки где-то рядом с папкой целевого проекта. В целевом проекте создаём файл PublishVersionSynchronizer.targets с таким контентом:
<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>
Делаем этому файлу BuildAction=«Content», открываем файл проекта, дописываем импорт этого файла:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /><!--После этой строки--> <Import Project="PublishVersionSynchronizer.targets" />
И всё работает.
Если кому-то нужны исходники, они на гитхабе.
Спасибо.
