views:

320

answers:

1

I am trying to use entity definitions in a config file to simplify the differences between the development, QA, UAT and production versions. Here is a sample of the beginning of my config file:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration [
    <!ENTITY MyStorageLocation "\\MyServer\MyStorageFolder">
    <!ENTITY MyDatabaseServer "devdb01">
]>
<configuration>
    <configSections>
 <section name="MyCustomSection" type="MyCustomSectionHandler,MyAssembly"/>
     ...
    </configSections>
<connectionStrings>
 <add name="MyConnectionString" providerName="System.Data.SqlClient" connectionString="Server=&MyDatabaseServer;;Database=MyDatabase;"/>
</connectionStrings>
    ...
<MyCustomSection>&MyStorageLocation;</MyCustomSection>
</configuration>

This seems to work fine, and why not as it is perfectly valid XML, as long as I don't use any of these entities in a custom configuration section that I end up calling ConfigurationManager.GetSection() on. Using the "MyDatabaseServer" entity in the connection string doesn't cause any problems. In the provided example, everything will work fine as long as I don't use the "MyStorageLocation" entity in the MyCustomSection element and then I only encounter the error when calling ConfigurationManager.GetSection() asking for the custom section.

My best guess is that the ConfigurationManager class is taking the raw source of the element and attempting to load that as XML, ignoring the declared entities for the entire XML file. Is there a better way to do this, short of re-coding the custom configuration sections to support references to settings instead of many absolute settings?

The error I recieve is:

2009-01-27 14:00:53,474 [11936] ERROR MyCustomWindowsService [(null)] - Errors starting service -- shutting down
System.Configuration.ConfigurationErrorsException: Reference to undeclared entity 'MyStorageLocation'. Line 183, position 19. (D:\...\MyCustomWindowsService.exe.config line 183) ---> System.Xml.XmlException: Reference to undeclared entity 'MyStorageLocation'. Line 183, position 19.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.HandleGeneralEntityReference(String name, Boolean isInAttributeValue, Boolean pushFakeEntityIfNullResolver, Int32 entityStartLinePos)
   at System.Xml.XmlTextReaderImpl.ResolveEntity()
   at System.Xml.XmlTextReader.ResolveEntity()
   at System.Xml.XmlLoader.LoadEntityReferenceNode(Boolean direct)
   at System.Xml.XmlLoader.LoadNode(Boolean skipOverWhitespace)
   at System.Xml.XmlLoader.LoadDocSequence(XmlDocument parentDoc)
   at System.Xml.XmlLoader.Load(XmlDocument doc, XmlReader reader, Boolean preserveWhitespace)
   at System.Xml.XmlDocument.Load(XmlReader reader)
   at System.Configuration.ErrorInfoXmlDocument.LoadFromConfigXmlReader(ConfigXmlReader reader)
   at System.Configuration.RuntimeConfigurationRecord.RuntimeConfigurationFactory.CreateSectionImpl(RuntimeConfigurationRecord configRecord, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentConfig, ConfigXmlReader reader)
   at System.Configuration.RuntimeConfigurationRecord.RuntimeConfigurationFactory.CreateSectionWithRestrictedPermissions(RuntimeConfigurationRecord configRecord, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentConfig, ConfigXmlReader reader)
   at System.Configuration.RuntimeConfigurationRecord.CreateSection(Boolean inputIsTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentConfig, ConfigXmlReader reader)
   at System.Configuration.BaseConfigurationRecord.CallCreateSection(Boolean inputIsTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentConfig, ConfigXmlReader reader, String filename, Int32 line)
   --- End of inner exception stack trace ---
   at System.Configuration.BaseConfigurationRecord.EvaluateOne(String[] keys, SectionInput input, Boolean isTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult)
   at System.Configuration.BaseConfigurationRecord.Evaluate(FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult, Boolean getLkg, Boolean getRuntimeObject, Object& result, Object& resultRuntimeObject)
   at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
   at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
   at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
   at System.Configuration.BaseConfigurationRecord.GetSection(String configKey, Boolean getLkg, Boolean checkPermission)
   at System.Configuration.BaseConfigurationRecord.GetSection(String configKey)
   at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
   at System.Configuration.ConfigurationManager.GetSection(String sectionName)
   at MyCustomWindowsService.Monitor() in D:\...\MyCustomWindowsService.cs:line 207
   at MyCustomWindowsService.Start() in D:\...\MyCustomWindowsService.cs:line 178

Deep in the bowels of the ConfigurationManager...

A: 

Perhaps your XML processor is ignoring the !ENTITY declarations because they are contained within an otherwise empty DTD.

Do you have a DTD you could use to validate this document? Lacking a DTD or schema, you can't really say the document is valid. Try running it through an online XML validation service like the one at URL: http://www.validome.org/xml/validate/

Here's an amended version of your document with a DTD I whipped up. See if this helps.


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration [
    <!ELEMENT configuration (configSections+, connectionStrings+, MyCustomSection)>

    <!ELEMENT configSections (section+)>

    <!ELEMENT section EMPTY>
    <!ATTLIST section name CDATA #REQUIRED>
    <!ATTLIST section type CDATA #REQUIRED>

    <!ELEMENT connectionStrings (add+)>

    <!ELEMENT add EMPTY>
    <!ATTLIST add name CDATA #REQUIRED>
    <!ATTLIST add providerName CDATA #REQUIRED>
    <!ATTLIST add connectionString CDATA #REQUIRED>

    <!ELEMENT MyCustomSection (#PCDATA)>

    <!ENTITY MyStorageLocation "\\MyServer\MyStorageFolder">
    <!ENTITY MyDatabaseServer "devdb01">
]>
<configuration>
  <configSections>
    <section name="MyCustomSection" type="MyCustomSectionHandler,MyAssembly"/>
  </configSections>
  <connectionStrings>
    <add name="MyConnectionString" providerName="System.Data.SqlClient" connectionString="Server=&MyDatabaseServer;;Database=MyDatabase;"/>
  </connectionStrings>
  <MyCustomSection>&MyStorageLocation;</MyCustomSection>
</configuration>
kmcorbett