After two years of preparation and accumulating confident experience from migrating several application projects, I recently started migrating one of the team's largest projects from. NET Framework 4.5 to. NET 6. This is a project that has been developed since 2016, with a maximum of more than 50 developers participating. The number of MR codes exceeds 10,000, and no one in the entire team can clearly explain all the functions in the project. This project references a large number of basic libraries within the team, many of which have been inactive for many years. This application project currently has nearly ten million users, and the migration process also requires preparing many remedies. Such a complex project naturally requires a lot of black technology to complete the implementation of. NET 6. This article will tell you what pits I stepped into during this process, what I learned, and why I did this.
previously
To be precise, I was actually the last mile in this process. I estimated the amount of work, which would take about 1.5 years to migrate this project from. NET Framework 4.5 to. NET 6. Although what I am saying now is that it took me five weeks to complete it, in fact, I did not count the preparation work before. What did previous work include? It also includes changing the major basic libraries to versions that support dotnet core, filling the differences between dotnet core and dotnet framework, such as the lack of IPCs such as. NET Remoting and WCF. Updated the packaging platform and build platform to support the building and packaging of dotnet core. The OTA for updating software is also the automatic software update function, which is used to support complex grayscale publishing functions and test. NET 6 environment support. Gradually move from the edge to the core, migrate application projects one by one, step in and accumulate experience.
After making sufficient preparations, with enough courage, and a good opportunity, with the support of the entire team, I began the last mile of migration.
In fact, before the final switch from. NET Framework 4.5 to. NET 6, the entire team, including me, had no idea that there were so many pits to be filled. No one knows how much weird black technology is used in this huge project. While writing this article, I told my partners that maybe no other team in the world would encounter our problems.
background
一个从 2016 时开始开发,最多有 50 多位开发者参与,而且这些开发者们没几位是省油的,有任何东西都需要自己造的开发者,有任何东西只要能用别人做好的绝不自己造的开发者,有写代码上过央视的开发者,有参与制定国家标准的开发者,有一个类里面一定要用满奇特的设计模式的开发者,有在代码注释里面一定要放大佛的开发者,有学到啥黑科技就一定要用上的开发者,有只要代码和人一个能跑就好的开发者,有睁着眼睛说瞎话代码和注释完全是两回事的开发者,有代码注释是文言文的开发者,有代码注释是全英文的开发者,有注释和文档远超过代码量的开发者,有中文还没学好的开发者,有喜欢挖坑而且必须自己踩的开发者,有啥东西都需要加日志的开发者,有十分帅穿着西装写代码的开发者,有穿着女装写代码的开发者,有在代码里面卖萌的开发者,有 这个函数只有我才能调用 的开发者,有相同的逻辑一定要用不同的方式实现的开发者,有在奔跑的坦克上换引擎的开发者。
During this migration process, there are still some pits that need to be filled. One of them is the best practices for client applications without multiple Exe entrances in dotnet core. This involves the conflict handling of multiple Exes and the conflict between the folder size after installation when the client application independently manages the runtime environment. This is also the focus of this article sharing.
This time also brings some requirements, including: to rely on the system to a minimum extent on the condition that the system environment is satisfied, and to ensure that it will not be affected by the dotnet runtime installed on the user's system. In addition, considering that multiple applications in the product line will need to share a runtime in the future, but this runtime cannot be shared by other teams or other companies to avoid being tampered with, some trial logic is needed. Finally, the version of WPF used requires customization, which means that some logic needs to be changed based on the officially released version to meet special product requirements.
This means redistributing dotnet to a library under full team control. After this change, and after updating to. NET 6, it is possible to implement full autonomous control of the dotnet framework, including the WPF framework. As a result, there are more things that can be done and less things that cannot be achieved.
In order to achieve more customization of WPF, I changed the status of the WPF framework from the original application runtime layer to the basic library layer. The status is the same as the basic components and other CBBs in the team, but it only exists as a bottom-level library, architecturally level with the bottom-level basic library.
The problems encountered this time are divided into two categories, one is caused by the complexity of the project itself, and the other is caused by dotnet. This article only records the problems caused by dotnet, and more of them are caused by special needs customization.
development architecture
In terms of the original application development architecture, the. NET Framework relied on existed as a system component. System components are affected by the system environment. In the domestic environment of monsters and ghosts, it is normal for system components to be modified and damaged. Applications using the. NET Framework have high customer service costs and need to help users solve environmental problems. As the number of users increases, the customer service costs of this part increase. This is one of the reasons why so many resources can be invested in updating projects.
The original application development architecture is layered as follows:

After updating to dotnet, the runtime is above the system layer. This design can reduce the impact of the system environment and solve a large number of application environment problems:

It can be seen from the above figure that WPF exists as part of the runtime, but this is not conducive to subsequent customization of WPF. My team expects to fully control WPF and deeply customize the WPF framework. Of course, my own team also has this ability, because I am also the official developer of the WPF framework. This part of the in-depth customization will be partially open source based on the customization.
The current development architecture hierarchy after the change is as follows:

Let WPF exist as part of the base library and no longer put it into the runtime. The plan is that multiple product projects within the product item share the. NET runtime, and individual products carry their own WPF load as the basic library.
the problems encountered
During the last-mile update, I encountered some problems with no best practices on the dotnet core mechanism.
Dependence issues on multiple AppHost entry applications
The client dependence problem of multiple Exe applications is one of the mechanisms. The project currently being migrated is an application of a multi-process model with many Exes. However, dotnet core currently does not have a best practice for perfectly sharing runtime between multiple Exes without being affected by the global dotnet runtime installed on the system, while taking into account the folder size after installation.
My list of problems is as follows:
- How to share runtime between multiple Exe files? If folders are not shared and published independently, the output folder will be very large.
- Multiple Exe files, if published in the same folder, will overwrite assemblies with the same name. According to dotnet's reference dependence policy, if there is a version incompatibility, a FileLoadException error will occur.
- Global assemblies shared by Program File cannot be used because the contents in this folder may be changed and damaged by applications from other companies, and the independent capabilities of the dotnet core environment cannot be used.
- Global assemblies shared by the Program File cannot be used because the dotnet runtime will be customized within the team, such as customizing the WPF assembly and changing the status of WPF from runtime to base library. This part of customization cannot contaminate other applications.
- Only a stable version can be selected for the runtime version released to the client, and developers will use a newer SDK version. The assemblies output by the development and build will reference the newer SDK version. If the application is running and loading only the runtime version released to the client, an error will occur because the version is lower than the build version.
- The runtime version published to the client is the runtime that contains a customized version, such as a customized WPF assembly. Custom WPF assemblies should be referenced at development time, but not runtime versions of clients that are lower than the build version.
In addition, since the dotnet core and the dotnet framework have institutional changes to exe, for example, the exe of dotnet core is just an apphost and does not contain IL data by default. The default exe under the dotnet framework contains application entries and IL data assemblies. This led to many unsupported parts in the original NuGet distribution. Fortunately, the hole in this part was smoothed out.
However, when customizing AppHost, it must conflict with NuGet distribution. Since NuGet implements unified distribution logic, if an Exe file is attached to the NuGet package, the content configured in this Exe file must not meet the specific project requirements.
Version dependency issues
In dotnet 6, the search logic of dependencies and the. NET Framework is different. In the. NET Framework, as long as there is a DLL with the same name, regardless of the version number. However, in dotnet 6, the actual DLL version number is greater than or equal to the DLL version that depends on the reference. The core conflict lies in the difference between the version of the runtime framework distributed to the client and the version of the SDK used by developers.
Why is this difference? The reason is that the SDK used by developers is basically the latest, but the runtime version distributed to the client does not have the courage to use the latest one.
To clarify this difference, you need to first clarify the concept:
- The SDK version used by developers, which is the official SDK version of dotnet, mostly uses the latest version, such as version 6.0.3.
- The runtime version of the client is distributed to the user. Most of the time, a relatively stable version is used, such as version 6.0.1.
- Private version, in order to re-customize the framework, for example, add your own business code to the WPF framework and distribute it by yourself. This version is also used as the runtime version of the client, but will only change based on a stable official release version of dotnet.
After updating to dotnet 6, we have the ability to fully control dotnet and can use our own private version of dotnet. Of course, the dotnet version also includes the WPF version. This means that you can customize the WPF framework enough to use your own customized WPF framework in the project.
However, using your own customized WPF framework is not without cost, and you will encounter differences between the runtime framework version distributed to the client and the SDK version used by developers. This difference will cause if the distributed version is a private version, which means that the private version must lag behind the version of the SDK used by the developer. Versions of the SDK that lag behind developers will have two problems.
- If you choose the developer's SDK version as the assembly loaded for software execution, then because the private version of the assembly will not be loaded, the private version will not be available during development. This means that the private version is difficult to debug, and behavioral changes in the private version cannot be handled at development time.
- If a private version is selected as the assembly loaded for software execution, the version number of the private version will be lower than the developer's SDK version, so the assembly built by the developer cannot find the corresponding version and will fail to run.
Current processing method
The current processing method is to add a reference to the customized assembly to the entry assembly of the application software during development, and output the customized assembly. This allows you to use a private version during development.
When building the server, set it so that the entry assembly of the application software no longer references the assembly of the custom part, so that all built assemblies do not contain references to the assembly of the custom part; during building, the reference to the assembly of the custom part is placed in the runtime folder and referenced by AppHost.
organizational documents
Code file organization
先将定制部分的程序集存放到代码仓库的 Build\dotnet runtime\ 文件夹里面,例如自定义的 WPF 框架就存放到 Build\dotnet runtime\WpfLibraries\ 文件夹里面。
接着将决定使用的 dotnet 运行时版本,放入到 Build\dotnet runtime\runtime\ 文件夹里面,此 runtime 文件夹的组织大概如下:
├─host
│ └─fxr
│ └─6.0.1
├─shared
│ ├─Microsoft.NETCore.App
│ │ └─6.0.9901
│ └─Microsoft.WindowsDesktop.App
│ └─6.0.9904
└─swidtag
Then overwrite the runtime folder with the customized assembly.
output file organization
The output file contains two concepts, which are the installation output folder where the installation package is installed on the user's device and the output folder during development. These two methods are different.
安装包安装到用户设备上的安装输出文件夹,例如输出到 C:\Program Files\Company\AppName\AppName_5.2.2.2268\ 文件夹。
The output folder is organized as follows:
├─runtime
│ ├─host
│ │ └─fxr
│ │ └─6.0.1
│ ├─shared
│ │ ├─Microsoft.NETCore.App
│ │ │ └─6.0.9901
│ │ └─Microsoft.WindowsDesktop.App
│ │ └─6.0.9904
│ └─swidtag
├─runtimes
│ ├─win
│ │ └─lib
│ │ ├─netcoreapp2.0
│ │ ├─netcoreapp2.1
│ │ └─netstandard2.0
│ └─win-x86
│ └─native
├─Resource
│
│ AppHost.exe
│ AppHost.dll
│ AppHost.runtimeconfig.json
│ AppHost.deps.json
│
│ App1.exe
│ App1.dll
│ App1.runtimeconfig.json
│ App1.deps.json
│
└─Lib1.dll
Why is the folder containing the runtime included in the application? Based on the following reasons:
- Due to the existence of multiple exEs, it is unrealistic to use independent publishing.
- Considering that multiple applications in the team may share a runtime in the future, rather than each application bringing its own, it is reasonable to put the runtime runtime into a public folder. However, since it is not yet stable, first Test it within the application.
- This Runtime folder contains its own customized content, which is slightly different from the official dotnet one, so it cannot be installed globally.
Since it is not suitable for independent publishing, nor is it suitable to place it in the Program File for overall purposes, it can only be placed in the application's own folder. In order to make the Runtime folder placed in the application's own folder recognized, you need to customize the AppHost file. For details, please refer to the following blog:
- 在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时 - walterlv
- 如何让 .NET 程序脱离系统安装的 .NET 运行时独立运行?除了 Self-Contained 之外还有更好方法!谈 dotnetCampus.AppHost 的工作原理 - walterlv
- 如何编译、修改和调试 dotnet runtime 仓库中的 apphost nethost comhost ijwhost - walterlv
开发时的输出文件夹是给开发者调试使用的,输出的文件夹是 $(SolutionDir)bin\$(Configuration)\$(TargetFramework) 文件夹,如 Debug 下的 dotnet 6 是输出到 bin\Debug\net6.0-windows 文件夹。在输出的文件夹的组织方式大概如下:
├─runtimes
│ ├─win
│ │ └─lib
│ │ ├─netcoreapp2.0
│ │ ├─netcoreapp2.1
│ │ └─netstandard2.0
│ └─win-x86
│ └─native
├─Resource
│
│ AppHost.exe
│ AppHost.dll
│ AppHost.runtimeconfig.json
│ AppHost.deps.json
│
│ App1.exe
│ App1.dll
│ App1.runtimeconfig.json
│ App1.deps.json
│
│ PresentationCore.dll
│ PresentationCore.pdb
│ PresentationFramework.dll
│ PresentationFramework.pdb
│ ...
│ PresentationUI.dll
│ PresentationUI.pdb
│ System.Xaml.dll
│ System.Xaml.pdb
│ WindowsBase.dll
│ WindowsBase.pdb
│
└─Lib1.dll
You can see that the folder for development-time output does not contain the Runtime folder, but places customized assemblies in the output folder, such as the customized WPF assembly content above. In this way, in addition to customized assemblies, other assemblies that can use the SDK can be realized during development. Please refer to the following reasons for doing this:
Modify project files
在入口程序集里面,加上对 定制部分的程序集 的引用逻辑,例如对定制的 WPF 的程序集,也就是放在 Build\dotnet runtime\WpfLibraries\ 文件夹里面的 DLL 进行引用和拷贝输出:
<ItemGroup> <Reference Include="$(SolutionDir)Build\dotnet runtime\WpfLibraries\*.dll"/>
<ReferenceCopyLocalPaths Include="$(SolutionDir)Build\dotnet runtime\WpfLibraries\*.dll"/>
</ItemGroup>
In this way, the customized version of the assembly can be referenced and output during development, thereby debugging the customized version of the assembly.
This is a feature of dotnet's SDK. If it is determined that if an assembly existing in the runtime framework has been referenced, this assembly will be used first over the framework's assembly. This is the basic support for the above code to replace the version of dotnet's SDK with a customized WPF assembly.
Since it is built on the server during actual release, in order to reduce the folder size after user installation, it is expected not to use files that reference the output of the customized version of the assembly in the entrance assembly, but only the version placed in the runtime folder to reduce duplicate files. Therefore, it is necessary to optimize the reference code of the entry assembly and set it to not be output when the server is built.
The implementation method is to set the properties through the msbuild parameter during server building, and determine the properties in the project file to know whether the server is built. If it is a server building, the assembly will not be referenced:
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' != '.NETFramework' And $(DisableCopyCustomWpfLibraries) != 'true'"> <Reference Include="$(SolutionDir)Build\dotnet runtime\WpfLibraries\*.dll"/>
<ReferenceCopyLocalPaths Include="$(SolutionDir)Build\dotnet runtime\WpfLibraries\*.dll"/>
</ItemGroup>
Please see below for details of modifying the build through the msbuild parameter.
There is a design flaw in the above method, that is, the logic used by developers will be different from the logic actually running on users, but I have not found any other way to solve so many problems.
modify and build
在服务器构建时,传入给 msbuild 的参数,加上 /p:DisableCopyCustomWpfLibraries=true 配置不要引用自定义版本的 WPF 框架。
然后在构建的时候,需要从 Build\dotnet runtime\runtime\ 文件夹拷贝定制的运行时放入到输出文件夹里面。
/// <summary>
/// 使用自己分发的运行时,需要从 Build\dotnet runtime\runtime 拷贝
/// </summary>
private void CopyDotNetRuntimeFolder()
{
var runtimeTargetFolder = Path.Combine(BuildConfiguration.OutputDirectory, "runtime");
var runtimeSourceFolder =
Path.Combine(BuildConfiguration.BuildConfigurationDirectory, @"dotnet runtime\runtime");
PackageDirectory.Copy(runtimeSourceFolder, runtimeTargetFolder);
}
That is to say, the entry assembly is not allowed to reference the custom version of the WPF framework, but instead let the application run to reference the ones in the runtime folder, thereby reducing duplicate files.
Reason for decision
The above solutions involve making complex decisions. Here are the reasons for each decision.
Solve runtime sharing between multiple Exe files
Multiple Exe files, and some Exe are stored in other folders, such as the Main folder. If these Exes are released independently, the installed output folder will be very large, and there will be many duplicate files, and the construction will take time.
The solution is to customize AppHost to have all Exes load the contents of the runtime folder of the application output folder. This enables runtime sharing between multiple Exe files.
In order to allow the Runtime folder placed in the application's own folder to be recognized and customize the AppHost file, please refer to the following blog for details:
- 在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时 - walterlv
- 如何让 .NET 程序脱离系统安装的 .NET 运行时独立运行?除了 Self-Contained 之外还有更好方法!谈 dotnetCampus.AppHost 的工作原理 - walterlv
- 如何编译、修改和调试 dotnet runtime 仓库中的 apphost nethost comhost ijwhost - walterlv
In addition to customizing the AppHost file to identify the Runtime folder, the second solution is to modify the file organization structure. The outermost layer is called the Main entry application folder, which only contains the main entry Exe file and its dependencies and runtime, while other Exes are placed in the inner folder. Exe placed in the inner folder cannot be directly executed externally, but can only be called indirectly by the outer entrance Exe. When the outer entrance Exe launches the Exe in the mileage folder, the environment variable informs the Exe's dotnet mechanism of the mileage folder to use the runtime content of the outermost entrance application folder called the Main entrance.
However, I didn't choose the second solution during this migration process. The fundamental reason is that there is a lot of ancient and boundary logic that has very strange calling methods. Putting the original Exe into the inner folder will naturally modify the relative path of Exe, which may cause a bunch of business modules to hang. There is also part of Exe that is started by other application software, and this part cannot be changed. Due to these requirements, we chose to place the Runtime folder further and change the AppHost file so that these executable files share the same privately deployed. NET runtime.
Solve the problem of customized version pollution of the overall situation
Customization of the dotnet runtime, such as customizing the WPF assembly, changes the status of the WPF assembly from the runtime to the base library. There are two ways to distribute this custom change to the user side.
- Bring it to the application itself, such as independent release of the application.
- Globally installed into the Program File.
In order not to pollute other companies 'applications, it cannot be installed globally into the Program File. It can only be brought to the application itself.
As above, it is inappropriate to publish each Exe independently, and can only be placed in the runtime folder of the output folder.
Call the plug-in process
Some plug-in processes are placed in the AppData folder, not in the application's installation output folder. How to call the plug-in process so that the plug-in process can use it until the runtime does not need to have the plug-in bring its own runtime.
实现方法是通过环境变量的方式,在 dotnet 里面,将会根据进程的环境变量 DOTNET_ROOT 去找运行时。
在主应用入口 Program 启动给应用自己加上环境变量,根据 dotnet 的 Process 启动策略,被当前进程使用 Process 启动的进程,将会继承当前进程的环境变量。从而实现了在使用主应用启动的插件进程,可以拿到 DOTNET_ROOT 环境变量,从而使用主应用的运行时。
/// <summary>
/// 加上环境变量,让调用的启动进程也自动能找到运行时
/// </summary>
static void AddEnvironmentVariable()
{
string key;
if (Environment.Is64BitOperatingSystem)
{
// https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables
key = "DOTNET_ROOT(x86)";
}
else
{
key = "DOTNET_ROOT";
}
// 例如调用放在 AppData 的独立进程,如 CEF 进程,可以找到运行时
var runtimeFolder =
Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase!, "runtime");
Environment.SetEnvironmentVariable(key, runtimeFolder);
}
根据官方文档,对 x86 的应用,需要使用 DOTNET_ROOT(x86) 环境变量。
详细请看 dotnet 6 通过 DOTNET_ROOT 让调起的应用的进程拿到共享的运行时文件夹
However, this method also has a clear disadvantage, that is, these plug-ins themselves cannot be run alone. If they are run alone, the runtime will not be found and will fail. The main entry process or other processes that get the runtime must execute the plug-ins by setting environment variables. Execution correctly.
此问题也是有解决方法的,解决方法就是在不污染全局的 dotnet 的前提下,将 dotnet 安装在自己产品文件夹里面,默认的 Program File 里面的应用文件夹布局都是 C:\Program File\<公司名>\<产品名> 的形式。于是可以将 dotnet 当成一个产品进行安装,于是效果就是如 C:\Program File\<公司名>\dotnet 的组织形式。如此即可以在多个应用之间通过绝对路径共享此运行时。
本次不采用文件夹布局为 C:\Program File\<公司名>\dotnet 的组织形式去解决问题,是因为当前使用的 dotnet 管理方法,以及正在迁移版本过渡中,再加上使用的私有的 WPF 也没有成熟,因此不考虑放在 C:\Program File\<公司名>\dotnet 的形式。而且也作为这个组织形式,需要考虑 OTA 软件更新的问题,以及更新过程中出错回滚等问题,需要更多的资源投入。但此方式可以作为最终形态。
Handle the issue where the developer's SDK version is higher than the runtime version you are going to send to the user
Problem encountered: The developer's SDK version is higher than the runtime version to be sent to the user. The DLL built at this time will reference the higher-version. NET assembly, so that when the developer is running, the corresponding version of the assembly will not be found.
Since writing App.config is invalid, you cannot use the previous method to combine multiple versions into one version. A solution is being sought, but still cannot be found.
There are two solutions tried: the first is to ask developers to install the same SDK version as the user's runtime and then set up a specific version through global.json. This can be solved, but it just requires the developer to install an additional SDK. The method to install the SDK is to decompress the files.
The first method requires each developer to install the old SDK version, and every time the SDK is updated, it needs to be repeated for each developer. This is not friendly to new developers because the developer needs to deploy the environment. However, if there is a new version of the dotnet SDK, the old version cannot be installed unless it is a preview version, which makes the deployment of developers more complicated. This is why the first method is not currently used.
Try the second method: in the entry assembly, reference the WPF customized version of the assembly, which will be output during the development build and referenced during the development run. When publishing, use the content in the runtime folder and delete the content in the output folder.
When publishing, the reason why the content in the runtime folder is used and the content in the output folder is to reduce the file size on the user side, because the content in the runtime folder is used and the customized version of the assembly file stored in the folder where the assembly entry is located is exactly the same. For example, after the customized version of the WPF assembly is released, it will take up about 30M more space on the user side, but this will not affect the size of the installation package.
The second method has the disadvantage of manually copying files every time a private version of WPF is released or a. NET version is updated. Maybe later versions can consider making NuGet distributions.
The second method cannot simply delete the content in the output folder. Instead, it needs to be packaged on the server so that the entry project is not referenced. Otherwise, the assembly will be deleted because the deps.json file references the assembly, and software execution will fail.
The following is an example of a configuration reference assembly for deps.json:
"PresentationFramework/6.0.2.0": {
"runtime": {
"PresentationFramework.dll": {
"assemblyVersion": "6.0.2.0",
"fileVersion": "42.42.42.42424"
}
},
"resources": {
"cs/PresentationFramework.resources.dll": {
"locale": "cs"
},
"de/PresentationFramework.resources.dll": {
"locale": "de"
},
"es/PresentationFramework.resources.dll": {
"locale": "es"
},
"fr/PresentationFramework.resources.dll": {
"locale": "fr"
},
"it/PresentationFramework.resources.dll": {
"locale": "it"
},
"ja/PresentationFramework.resources.dll": {
"locale": "ja"
},
"ko/PresentationFramework.resources.dll": {
"locale": "ko"
},
"pl/PresentationFramework.resources.dll": {
"locale": "pl"
},
"pt-BR/PresentationFramework.resources.dll": {
"locale": "pt-BR"
},
"ru/PresentationFramework.resources.dll": {
"locale": "ru"
},
"tr/PresentationFramework.resources.dll": {
"locale": "tr"
},
"zh-Hans/PresentationFramework.resources.dll": {
"locale": "zh-Hans"
},
"zh-Hant/PresentationFramework.resources.dll": {
"locale": "zh-Hant"
}
}
},
The solution to the above problem is to do the same as the above method, using different reference relationships in developer builds and server builds.
Handle assembly issues that users load globally
background
In dotnet, version evaluation will be carried out, and policy logic will be carried out based on Roll forward, assuming that the default Minor policy is followed. The priority is to find the Runtime folder recorded in AppHost, and then to find the dotnet folder in Program File. Take an appropriate version number. If the application is currently packaged in 6.0.1, and the user installs version 6.0.3 in the Program File, then the 6.0.3 version of the Program File will be selected.
This means that if the 6.0.3 version of the user's Program File is corrupt, the application will be allowed to use the corrupted file.
Therefore, it is impossible to use dotnet to deal with environmental issues.
The expectation is that instead of automatically loading the global assembly Program File on the user side, you can use the assembly in the runtime folder brought by the application itself.
processing method
This problem can be solved by making the version number of the dotnet folder of the application's Runtime high enough.
Change the folder of dotnet placed in the application's Runtime to version 6.0.990x, and the last x is the official Minor version number corresponding to the original dotnet. For example, 6.0.1 corresponds to the 6.0.9901 version number.
According to Roll forward's logic, version 6.0.990x will be judged to be the highest version, so the global assembly Program File will not be loaded.
详细请看 https://docs.microsoft.com/en-us/dotnet/core/versions/selection
debugging method
Modifying the Runtime folder loading path requires debugging. Since developers have installed the SDK environment in most cases, this also prevents developers from debugging well on their own devices. The reason is that if your own Runtime folder is configured incorrectly, AppHost will be loaded into the SDK environment by default, so it can run as expected on the developer's device.
However, on the user's device, there is no environment or it is damaged, so the application will not be able to run.
One way to debug on a developer's device is to add environment variables and load references for output through dotnet's own AppHost debugging method.
假设要测试的应用是 App.exe 文件,可以打开 cmd 先输入以下命令,用于给当前的 cmd 加上环境变量,如此做可以不污染开发环境:
set COREHOST_TRACE=1
set COREHOST_TRACEFILE=host.txt
设置完成之后,再通过命令行调用 App.exe 文件,此时的 App.exe 文件将会输出调试信息到 host.txt 文件:
App.exe
The content of a debugging message is as follows:
--- The specified framework 'Microsoft.WindowsDesktop.App', version '6.0.0', apply_patches=1, version_compatibility_range=minor is compatible with the previously referenced version '6.0.0'.
--- Resolving FX directory, name 'Microsoft.WindowsDesktop.App' version '6.0.0'
Multilevel lookup is true
Searching FX directory in [C:\lindexi\App\App\runtime]
Attempting FX roll forward starting from version='[6.0.0]', apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, prefer_release=1
'Roll forward' enabled with version_compatibility_range [minor]. Looking for the lowest release greater than or equal version to [6.0.0]
Found version [6.0.1]
Applying patch roll forward from [6.0.1] on release only
Inspecting version... [6.0.1]
Changing Selected FX version from [] to [C:\lindexi\App\App\runtime\shared\Microsoft.WindowsDesktop.App\6.0.1]
Searching FX directory in [C:\Program Files (x86)\dotnet]
Attempting FX roll forward starting from version='[6.0.0]', apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, prefer_release=1
'Roll forward' enabled with version_compatibility_range [minor]. Looking for the lowest release greater than or equal version to [6.0.0]
Found version [6.0.1]
Applying patch roll forward from [6.0.1] on release only
Inspecting version... [3.1.1]
Inspecting version... [3.1.10]
Inspecting version... [3.1.20]
Inspecting version... [3.1.8]
Inspecting version... [5.0.0]
Inspecting version... [5.0.11]
Inspecting version... [6.0.1]
Inspecting version... [6.0.4]
Attempting FX roll forward starting from version='[6.0.0]', apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, prefer_release=1
'Roll forward' enabled with version_compatibility_range [minor]. Looking for the lowest release greater than or equal version to [6.0.0]
Found version [6.0.1]
Applying patch roll forward from [6.0.1] on release only
Inspecting version... [6.0.4]
Inspecting version... [6.0.1]
Changing Selected FX version from [C:\lindexi\App\App\runtime\shared\Microsoft.WindowsDesktop.App\6.0.1] to [C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.4]
Chose FX version [C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.4]
从 --- 开始,就是加载各个负载,如桌面等。开始读取的寻找文件夹是放在 AppHost 里面的配置,这是通过 在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时 - walterlv 的方法设置的,让应用去先寻找 runtime 文件夹的内容,如上文的文件布局。
Then in dotnet, the Roll forward policy read is the value of minor. Next, we find the content of version 6.0.1 and placed in the runtime folder:
'Roll forward' enabled with version_compatibility_range [minor]. Looking for the lowest release greater than or equal version to [6.0.0]
Found version [6.0.1]
As the first thing found, it will be the default runtime folder:
Changing Selected FX version from [] to [C:\lindexi\App\App\runtime\shared\Microsoft.WindowsDesktop.App\6.0.1]
接着继续寻找 C:\Program Files (x86)\dotnet 文件夹:
Searching FX directory in [C:\Program Files (x86)\dotnet]
I found many versions in the global folder, and I found many versions and compared them with the default runtime folder to find the most suitable one.
As in the above code, it was found that 6.0.4 was more suitable than the default 6.0.1, so I modified the version of the currently found runtime folder to 6.0.4:
Changing Selected FX version from [C:\lindexi\App\App\runtime\shared\Microsoft.WindowsDesktop.App\6.0.1] to [C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.4]
Since there were no other folders to find, 6.0.4 was used as the runtime folder:
Chose FX version [C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.4]
In this way, you can learn that the runtime folders you let your application find meet expectations.
These are the pitfalls you hit in migrating this application and the decisions you make. I hope it will help everyone in their migration.