Click here to Skip to main content
15,868,016 members
Home / Discussions / .NET (Core and Framework)
   

.NET (Core and Framework)

 
GeneralRe: Deploying a .NET App with Nuget Dependencies Pin
TNCaver1-Nov-21 6:25
TNCaver1-Nov-21 6:25 
GeneralRe: Deploying a .NET App with Nuget Dependencies Pin
Richard Deeming1-Nov-21 22:49
mveRichard Deeming1-Nov-21 22:49 
GeneralRe: Deploying a .NET App with Nuget Dependencies Pin
TNCaver2-Nov-21 6:51
TNCaver2-Nov-21 6:51 
QuestionConfiguration.Save Writes Invalid Configuration File Pin
Member 1266077621-Oct-21 16:45
Member 1266077621-Oct-21 16:45 
AnswerRe: Configuration.Save Writes Invalid Configuration File Pin
Dave Kreskowiak22-Oct-21 5:20
mveDave Kreskowiak22-Oct-21 5:20 
GeneralRe: Configuration.Save Writes Invalid Configuration File Pin
Member 1266077622-Oct-21 7:09
Member 1266077622-Oct-21 7:09 
GeneralRe: Configuration.Save Writes Invalid Configuration File Pin
Member 154156281-Nov-21 18:04
Member 154156281-Nov-21 18:04 
AnswerDebrief: Configuration.Save Writes Invalid Configuration File Pin
Member 1266077627-Oct-21 10:18
Member 1266077627-Oct-21 10:18 
After laborious effort I was able to understand what was happening. Generally, I would consider the behavior in the OP as a bug with the .NET code since it can't read in what it writes out; however, the code seems structured and designed to produce this behavior; therefore, the behavior doesn't appear to be a mistake, just a sore and undocumented design with unintuitive behavior. Since this is such odd, undocumented and unintuitive behavior I felt compelled to share my findings with posterity.

Here is what I found:

Public APIs:

System.Configuration.ConfigurationSectionGroup

This is the configuration construct used to group sections and other groups, essentially a container. It can be subclassed but itself derives from nothing and offers little functionality so it offers little opportunity for customization.

System.Configuration.Configuration

Similar to ConfigurationSectionGroup in that it essentially acts as a container for sections and groups and derives from nothing; however, it is sealed and cannot be subclassed. It provides contextual information, some fundamental constructs for processing connection strings and application settings and the API for saving the configuration to file. It provides some flexibility for customization via delegate actions.

So, by and large, most of the processing of configuration files is behind the scenes and internal to the .NET framework. Therefore, to study these mechanisms required looking into the .NET code base and decompiling the necessary code in the VS debugger.

System.Configuration.MgmtConfigurationRecord
System.Configuration.BaseConfigurationRecord

These classes are internal to the .NET framework and provide much of the guts of configuration management. Simply, each configuration [file] is associated with one configuration record. The configuration record provides bookkeeping of what is in the configuration file and how the configuration information is written.

SaveAs(string filename, ConfigurationSaveMode saveMode, bool forceUpdateAll)

This method is called whenever a user wants to save the configuration file. It manages the save process, essentially providing a wrapper around save functionality and providing error handling. It calls CopyConfig(...) whose job it is to copy over all original configuration file content (comments and all) and integrate the new changes. It calls the following methods:

CopyConfigDeclarationsRecursive(...)

This method copies all content from the declarations section

XML
<configuration>
  <configSections>
    <!-- Declaration of sections and section groups -->
  </configSections>
</configuration>


of the original configuration file. It then calls

WriteUnwrittenConfigDeclarations(...)

which writes any new declarations.

Herein lies the rub! WTF | :WTF:

NEW SECTIONS AND SECTION GROUPS ARE WRITTEN AFTER EXISTING SECTIONS AND GROUPS.

Intuitive, yes? Wrong!

Consider the configuration file XML from the OP:

XML
<configuration>
  <configSections>
    <sectionGroup name="ServiceConfiguration" type="My.ServiceConfiguration" >

      <section name="CustomSection" type="My.CustomSection" />

      <sectionGroup name="ServiceStepGroups" type="My.StepGroups" >

        <sectionGroup name="MyGroup1" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
    <sectionGroup name="ServiceConfiguration" >  <!-- No type attribute and wrong place -->

      <sectionGroup name="ServiceStepGroups" >   <!-- No type attribute and wrong place -->

        <sectionGroup name="MyGroup2" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
  </configSections>

  <ServiceConfiguration>
    <ServiceStepGroups>
      <CustomSection />
      <MyGroup1 />
      <MyGroup2 />     <!-- This is in expected place -->
    </ServiceStepGroups>
  </ServiceConfiguration>
</configuration>


We have the main custom section group ServiceConfiguration which holds all business-type configuration information which is unique or specific to the application (as opposed to standard types of configuration constructs such application settings, runtime, service model, etc.). Inside this main group we have the ServiceStepGroups group which in turn declares various groups of steps for the application to perform (MyGroup1 and MyGroup2).

However, there are two (2) ServiceConfiguration declarations, a clear violation of the configuration specification which requires uniquely named groups. In the original configuration file we only had MyGroup1 defined where it and its containing groups were declared with the type attribute, necessary for declaring the .NET group type when loading a configuration file. But, we added MyGroup2 after loading the configuration file so that we could re-save the configuration file with the new group.

Here begins the unintuitive behavior Confused | :confused: .

Because CopyConfigDeclarationsRecursive(...) is called first it writes a complete ServiceConfiguration declaration as copied from the original file. When WriteUnwrittenConfigDeclarations(...) is called it has no choice but to write a second ServiceConfiguration declaration in order to maintain the declaration hierarchy; however, it does so without type attributes, presumably since the .NET type is not required for writing but only when reading. If a declaration (such as ServiceStepGroups) was not present in the original declaration then the type is written; if a declaration was present a type is not written.

So, this behavior would seem to be a design flaw in SaveAs(...). However, in the interest of open-mindedness I will hold back eternal judgment since there may be competing objectives or other behavioral scenarios of which I am not aware which drove Microsoft's design. Nonetheless, I think we can all agree that better documentation would have been helpful in understanding the behavior and function of programmatically modifying and saving configuration files. If there is better documentation I could not find it.

The Solution:

After all was discovered and understood I was able to produce a proper configuration file by implementing a post-processing step Shucks | :-\ . It was a workaround: manually modify the XML of the new configuration file (I used the System.Xml libraries) to move new group declarations to their proper place within the original ServiceStepGroups declaration and remove the duplicate hierarchical constructs.

I could have done this in the beginning but I had assumed D'Oh! | :doh: I didn't need to (based on expectations of intuitive functionality and lack of documentation) and that the problem was with me and my code utilizing the .NET configuration mechanisms. While vindicated technically it was a good (but agonizing Dead | X| ) lesson in the underlying mechanisms of .NET configuration processing and its limitations.

So ending on a positive note Big Grin | :-D maybe this technical dive and case study can provide some documentation or reference to others experiencing a similar issue in the future. I am reminded of a quote from John Adams:

> Posterity! you will never know how much it cost the present generation to preserve your freedom.
> I hope you will make a good use of it. If you do not, I shall repent in Heaven that I ever
> took half the pains to preserve it

Posterity, I hope you make good use of this information. Smile | :)

modified 28-Oct-21 16:29pm.

GeneralRe: Debrief: Configuration.Save Writes Invalid Configuration File Pin
Peter_in_278027-Oct-21 10:54
professionalPeter_in_278027-Oct-21 10:54 
GeneralRe: Debrief: Configuration.Save Writes Invalid Configuration File Pin
Member 1266077627-Oct-21 11:20
Member 1266077627-Oct-21 11:20 
GeneralRe: Debrief: Configuration.Save Writes Invalid Configuration File Pin
Peter_in_278027-Oct-21 11:40
professionalPeter_in_278027-Oct-21 11:40 
GeneralRe: Debrief: Configuration.Save Writes Invalid Configuration File Pin
phil.o27-Oct-21 11:46
professionalphil.o27-Oct-21 11:46 
QuestionMVC Controller State Managment Pin
Kunal Mandloi13-Sep-21 22:18
Kunal Mandloi13-Sep-21 22:18 
AnswerRe: MVC Controller State Managment Pin
Richard Deeming13-Sep-21 22:26
mveRichard Deeming13-Sep-21 22:26 
AnswerRe: MVC Controller State Managment Pin
Kunal Mandloi14-Sep-21 4:18
Kunal Mandloi14-Sep-21 4:18 
QuestionFramework v. Core for Windows App Pin
Richard Andrew x644-Sep-21 1:47
professionalRichard Andrew x644-Sep-21 1:47 
AnswerRe: Framework v. Core for Windows App Pin
Dave Kreskowiak4-Sep-21 4:46
mveDave Kreskowiak4-Sep-21 4:46 
AnswerRe: Framework v. Core for Windows App Pin
Richard Deeming5-Sep-21 22:10
mveRichard Deeming5-Sep-21 22:10 
QuestionUsing Postman to test HTTPPatch in .Net Core API, [FromBody] JsonPatchDocument<TEntity> patchEntity is null Pin
thewizardoftn27-Aug-21 9:52
thewizardoftn27-Aug-21 9:52 
AnswerRe: Using Postman to test HTTPPatch in .Net Core API, [FromBody] JsonPatchDocument<TEntity> patchEntity is null Pin
Richard Deeming30-Aug-21 22:03
mveRichard Deeming30-Aug-21 22:03 
AnswerRe: Using Postman to test HTTPPatch in .Net Core API, [FromBody] JsonPatchDocument<TEntity> patchEntity is null Pin
Matt T Heffron31-Aug-21 11:45
professionalMatt T Heffron31-Aug-21 11:45 
QuestionVisual Studio 2019 - unable to add data connection Pin
Member 1299459619-Aug-21 1:49
Member 1299459619-Aug-21 1:49 
AnswerRe: Visual Studio 2019 - unable to add data connection Pin
Richard MacCutchan19-Aug-21 2:27
mveRichard MacCutchan19-Aug-21 2:27 
QuestionVisual studio 2019 Pin
Member 1105128618-Aug-21 19:57
Member 1105128618-Aug-21 19:57 
AnswerRe: Visual studio 2019 Pin
Richard Deeming18-Aug-21 22:03
mveRichard Deeming18-Aug-21 22:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.