dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.92k stars 4.64k forks source link

Application Settings migration from netframework to netcore #31482

Open shvez opened 4 years ago

shvez commented 4 years ago

Hi, there. Let me start with a short description. We have a native application that loads .netframework runtime and assemblies. Every assembly represents some application and every application is loaded to its own app domain even if it is from the same assembly. We have used app.config file with corresponding settings classes. That was working fine.

Now we have a net core application that should do a similar thing. It should load the assemblies from above recompiled with minimum changes as net core assemblies. That minimum of changes required because we still need to run our native application with .netframework assemblies while we transit to the new net core application.

There are several questions:

  1. How Settings classes should be transformed to be able to read data. Currently, they just provide default values
  2. It seems like System.Configuration.ConfigurationManager is not aware of multi assembly load context applications.
  3. How to transform custom settings classes

I've prepared a small sample that does what we do Loader.zip

I'm using netcoreapp3.0

shvez commented 4 years ago

About sample. Everything happens inside Application.Run method. During the first start, it should fail in Application.cs:29 because System.Configuration.ConfigurationManager is not shared with 'Default' context. that can be fixed by uncommenting line AssemblyLoadContext.cs:36

Next step is to get non-default values in line Application.cs:21 and Application.cs:24

@vitek-karas here is the report about ConfigurationManager issues

vitek-karas commented 4 years ago

The best way to avoid duplicate copies of assemblies for the plugin is to modify the project/package references. So in the Application.csproj you can change the references to look like this:

  <ItemGroup>
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.6.0">
      <ExcludeAssets>runtime</ExcludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\AppInterfaces\AppInterfaces.csproj">
      <Private>false</Private>
      <ExcludeAssets>runtime</ExcludeAssets>
    </ProjectReference>
  </ItemGroup>

Then you don't need any special cases in the resolver as the files will not get copied over to the output.

That doesn't answer why the configuration is not loaded though. That said the usage of statics like ConfigurationManager.GetSection will not work as statics are shared then (only one copy for the entire app). To get multiple copies you would need to get mutliple copies of the System.Configuration.ConfigurationManager - which is in theory possible, but definitely not recommended.

Just curious: What is the scenario you're trying to solve? If these are logically separate applications, why not run them as separate processes? That is the best level of isolation. Neither AppDomain nor AssemblyLoadContext really provide true isolation (other than some of the assembly resolution isolation).

shvez commented 4 years ago

Thanks a lot for the update.

What is the scenario you're trying to solve? So far we had a socket server native host application that loads managed applications as plugins. We had only one process that is able to load many applications and share ports between them. now when we have a netcoreapp3 host application we still have to keep the option of port sharing.

all these hassles about isolation are just because we have statics in our code.

Another thing that complicates is that we have public sdk that is used by customers a lot for writing their own applications. Some of them extend our stuff, some write their own apps from scratch. So basically I do not have any clue what are they doing but we try to be as backward compatible as possible to reduce upgrade complexities.

we managed to get configuration stuff working but a solution requires some extra work from us and from developers.

we do it next way:

  1. Using current domain properties we pass the config file name from the host app to the plugin app. (In that regard I was thinking about AssemblyLoadContext prperties)
  2. we load the configuration using System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None) call
  3. For ApplicationSettingsBase children we get section like this
            var t = typeof(T);//T is derived from ApplicationSettingsBase
            var sectionName = "applicationSettings/" + t.Namespace + "." + t.Name;
            var clientSection = config.GetSection(sectionName) as ClientSettingsSection;
  4. then we get all public properties from instance of T and set every property to its value doing all necessary convertations

For classes derived from ConfigurationSection like WebRpcSettings in the sample we open config file using XMLDocument get XmlReader for corresponding section and then inside a constructor of we call DeserializeSection(xmlReader).

I assume and hope that we do something wrong and there are better and easier way :)

vitek-karas commented 4 years ago

Thanks a lot for the description - I was mostly wondering "Why do you think you need to provide isolation?". Statics are a reason, but they imply that you expect your assemblies to be used in "weird" ways (as in loaded potentially twice and so on), ALCs will typically not help with that as by the time the code gets to you, it might already be too late.

shvez commented 4 years ago

EDT: Assembly 'Application' from the sample in our code is very basic assembly that is used to build real applications. So, it can be loaded twice or trice

ALCs will typically not help with that as, by the time the code gets to you, it might already be too late.

this part I did not really get.

I was mostly wondering

I hope there are more guys working with you and someone who is guru of ConfigurationManager could help :)

joperezr commented 4 years ago

@jeffschwMSFT do we have any docs on how to port code from .NET Framework to .NET Core when using app domains to load assemblies? I suppose in .NET Core this would be using ALCs which would already take the deps.json file into consideration

vitek-karas commented 4 years ago

We don't have a direct guide for migration, but there are pretty good docs on AssemblyLoadContext and related classes and how to use them in various scenarios: https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/overview https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext