XML Settings in Vb.Net 2.0
In .Net 2.0 zijn behoorlijke veranderingen doorgevoerd op gebied van het opslaan van instellingen in een XML-configuratiebestand. Dit artikel gaat in op de vernieuwingen en geeft inzicht in de verschillende manieren waarop er in .Net 2.0 omgegaan kan worden met het nieuwe ConfigurationManager-object.
Untyped, of toch maar typed?
Het grootste verschil tussen .Net 1.1 en .Net 2.0 is dat de instellingen in .Net 1.1 untyped werden opgeslagen, terwijl deze in .Net 2.0 nu ook typed kunnen worden opgeslagen. Een voorbeeld van een van de voordelen hiervan is, dat een opgeslagen numerieke waarde niet later als string opgevraagd gaat worden. Maar ook meer complexe .Net objecten kunnen als instelling worden opgeslagen. Daarnaast is door de strong typing (en de daarbij behorende collection definities) Intellisense toegevoegd, zodat alle configuratie instellingen eenvoudig en zonder fouten in de objectnaam opgevraagd kunnen worden. Ook is het nu mogelijk om via een enumerator alle instellingen te tonen, terwijl je in .Net 1.1 zelf de namen van alle instellingen moest weten.
Tot zover het grootste verschil in settings tussen .Net 1.1 en .Net 2.0.
Untyped, of toch maar typed?
AppSettings
In .Net 2.0 is een aantal .Net 1.1 classes ‘obsolete’ geworden waaronder de ConfigurationSettings-klasse uit de namespace System.Configuration. De klasse functioneert nog wel, maar in Visual Studio 2005 krijg je een ‘warning’ met het verzoek om de ConfigurationManager-klasse te gebruiken. Je kunt de overstap gedeeltelijk maken door alleen de ConfigurationManager-klasse te gaan gebruiken, zonder ook direct over te stappen naar een andere manier van het opslaan van instellingen (via het typed model). Untyped ‘AppSettings’ worden door de ConfigurationManager-klasse namelijk nog steeds ondersteund. De volgende regel is afkomstig uit .Net 1.1:
System.Configuration.ConfigurationSettings.AppSettings_
("UserName").ToString()
In .Net 2.0 moet deze regel aangepast worden naar:
System.Configuration.ConfigurationManager.AppSettings_
("UserName").ToString()
Deze regel geeft uit een App.Config bestand het ‘Username’ element, maar afgezien van een nieuwe klasse naam is op deze regel geen sprake van nieuwe functionaliteit. Toch biedt het gebruik van de ConfigurationManager-klasse meer voordelen, ook al maak je geen gebruik van een typed collectie. In .Net 1.1 was het vrij lastig om aanpassingen in de instellingen op te slaan in een App.Config bestand. De ConfigurationManager-klasse biedt een ‘Save’ en een ‘SaveAs’ methode waarmee instellingen definitief kunnen worden opgeslagen.
Dim config As Configuration = _
ConfigurationManager.OpenExeConfiguration(_
ConfigurationUserLevel.None)
Dim userNameElement As KeyValueConfigurationElement = _
New KeyValueConfigurationElement("UserName")
config.AppSettings.Settings.Add(userNameElement)
userNameElement.Value = "Administrator"
config.Save(ConfigurationSaveMode.Minimal, False)
Bovenstaande code laadt de configuratie die gekoppeld is aan de lopende applicatie. Als parameter van de OpenExeConfiguration-method kan nog ingesteld worden wat voor soort configuratie geladen moet worden.
| ConfigurationUserLevel.None |
laadt de configuratie voor alle gebruikers (application settings) |
| ConfigurationUserLevel.PerUserRoamingAndLocal |
laadt de lokale configuratie voor de huidige gebruiker (De naam is nog niet consistent, er is geen roaming ondersteuning bij deze keuze) |
| ConfigurationUserLevel.PerUserRoaming |
laadt de roaming configuratie voor de huidige gebruiker |
Tabel 1: Configuratie-soorten
Als er gekozen wordt voor PerUserRoamingAndLocal of PerUserRoaming level, wordt niet de app.exe.Config file gebruikt maar de user.Config. Dat file bevindt zich in principe in de “Documents and Settings “-map. In dit voorbeeld wordt ‘None’ gebruikt als ConfigurationUserLevel; het uitvoeren van de Save() method resulteert dan in het opslaan van settings naar de app.exe.Config file.
Aan de huidige configuratie wordt een userNameElement ‘UserName’ toegevoegd met als value ‘Administrator’. Hierna worden de instellingen opnieuw opgeslagen. Reeds bestaande instellingen worden opnieuw opgeslagen, inclusief het nieuwe ‘UserName’ element.
| ConfigurationSaveMode.Minimal |
Alleen opslaan van die properties die verschillen van de originele waarden |
| ConfigurationSaveMode.Modified |
Alleen opslaan van die properties die veranderd zijn ten opzichte van de originele waarden, ook als die inmiddels weer gelijk zijn aan de originele waarden. |
| ConfigurationSaveMode.Full |
Opslaan van alle properties; dit geeft een dump van alle .Net instellingen en is voor normaal gebruik af te raden, aangezien er een bijzonder groot configuratie-file ontstaat. |
Tabel 2: Configuratie-SaveModes
Het is mogelijk dat bij het opslaan een ConfigurationErrorsException ontstaat. Die kan als oorzaak een algemeen schrijfprobleem hebben, maar in veel gevallen is deze het gevolg van het al veranderd zijn van de configuratie door een andere gebruiker. De tweede parameter ‘ForceUpdateAll’ bij de Save-method is op dit moment helaas nog ongedocumenteerd, maar ik vermoed dat deze parameter er voor zorgt dat, ondanks een eventuele verandering door een andere gebruiker van de configuratie, het opslaan toch plaatsvindt.
Na het opslaan zijn aan de configuratie file de volgende regels toegevoegd:
Let op: Om gebruik te kunnen maken van de ConfigurationManager-klasse is het noodzakelijk om behalve een ‘Using’ statement ook een reference in het project te maken naar de System.Configuration assembly. Door die reference worden er ineens extra eigenschappen van de ConfigurationManager-klasse getoond.
app.Config of app.exe.Config ?
Instellingen in het app.Config-file worden bij het compileren in Visual Studio overgenomen in het app.exe.Config-file. Gebruik je Visual Studio niet (bijvoorbeeld in een deployment situatie), dan heeft het geen zin om het app.Config-file te gebruiken. Het is daarom aan te raden om dat file bij deployment te verwijderen (dan wel niet mee te sturen), aangezien de aanwezigheid van dat bestand alleen maar tot verwarring leidt en het bestand toch niet uitgelezen wordt.
Ik kwam hierachter omdat het opslaan van instellingen naar een app.Config-file niet mogelijk bleek in .Net 2.0, ondanks de verschillende enthousiaste blogs waarin stond dat het opslaan van instellingen wel zou functioneren. Het uitvoeren van de eerder in dit artikel beschreven Save-method slaat gegevens automatisch en altijd op in het app.exe.Config-file (of in user.Config). Ook het opvragen van instellingen raadpleegt altijd datzelfde configuratie-file.
De reden voor de keuze om op te slaan in de hierboven genoemde .Config bestanden is na enige research logisch gebleken: Microsoft heeft het de ontwikkelaar makkelijk willen maken door instellingen in de app.Config te kunnen aanbrengen vanuit de VS IDE. Die instellingen komen na het compileren en starten vanuit Visual Studio automatisch in de juiste .Config-files terecht. Dat kan een ‘Debug’- of een ‘Release’-configuratie zijn. Visual Studio zorgt ervoor dat beide .Config-files telkens automatisch worden bijgewerkt.
Verder op in dit artikel bij ‘Project settings’ kom ik nogmaals terug op het app.Config-bestand.
Typed settings
Behalve het eenvoudig op kunnen slaan van untyped ‘appSettings’ voorziet .Net 2.0 in typed settings. Bovendien kunnen verschillende sectiegroepen worden aangemaakt, zodat instellingen te splitsen zijn in logische secties. Om te kunnen werken met typed instellingen moeten er eerst een of meerdere klasses ontworpen worden waarin het settings-model wordt gedefinieerd. Optioneel kan gebruik gemaakt worden van een op ConfigurationSectionGroup gebaseerde klasse, maar verplicht is in ieder geval de op ConfigurationSection gebaseerde klasse. In die laatste bestaat een configuratie-sectie met eventuele attributen. Onder deze sectie kunnen desgewenst nog configuratie-properties worden toegevoegd. Onderstaande code beschrijft een ConfigurationSectionGroup en een onderliggende ConfigurationSection-klasse.
Imports System.Configuration
Public Class CustomSection:Inherits ConfigurationSection
"UserName",_
DefaultValue:="Administrator", _
RequiredValue:=True)> _
Public Property UserName() As String
Get
Return CStr(MyBase.Item("UserName"))
End Get
Set(ByVal value As String)
MyBase.Item("UserName") = value
End Set
End Property
End Class
' Define the above defined CustomSection in
' a CustomSectionGroup
Public NotInheritable Class CustomSectionGroup :_
Inherits ConfigurationSectionGroup
Public ReadOnly Property Custom() As CustomSection
Get
Return CType(Sections.Get("CustomSection"),_
CustomSection)
End Get
End Property
End Class
In bovenstaande code wordt een CustomSection gedefinieerd met een eigen attribuut ‘UserName’ van het type ‘string’.
Nu de CustomSection-klasse gedefinieerd is, kan een UserName worden toegevoegd aan de actieve configuratie.
Dim config As Configuration = _
ConfigurationManager.OpenExeConfiguration(_
ConfigurationUserLevel.None)
Dim customSectionGroupObj As CustomSectionGroup
If config.SectionGroups("CustomGroup") Is Nothing Then
customSectionGroupObj = New CustomSectionGroup()
config.SectionGroups.Add(_
"CustomGroup", customSectionGroupObj)
customSectionGroupObj.ForceDeclaration(True)
End If
Bovenstaande code maakt een nieuwe SectionGroup aan gebaseerd op de CustomSectionGroup-klasse zoals eerder weergegeven. De sectie krijgt als naam ‘CustomGroup’ maar mag natuurlijk elke naam hebben.
Dim customGroup As ConfigurationSectionGroup
customGroup = config.SectionGroups.Get("CustomGroup")
Dim customSection As CustomSection
If customGroup.Sections.Get("CustomSection") Is Nothing_
Then
customSection = New CustomSection()
customGroup.Sections.Add(_
"CustomSection", customSection)
customSection.SectionInformation.ForceSave = True
End If
De code hierboven maakt een Section aan, gebaseerd op de CustomSection-klasse zoals eerder weergegeven. De sectie krijgt als naam ‘CustomSection’ maar mag ook in dit geval weer elke naam hebben.
Dim customSectionObj As CustomSection
customSectionObj =_
customGroup.Sections.Get("CustomSection")_
as CustomSection
customSectionObj.UserName = "TestUser";
config.Save(ConfigurationSaveMode.Minimal)
Het laatste blok code instantieert de CustomSection-klasse en vult die met de ‘CustomSection’ -section uit het configuratie-file via een cast. In het customSectionObj wordt de UserName-property vervolgens aangepast naar ‘TestUser’ en wordt de totaal aangemaakte configuratie opgeslagen.
Het resultaat is een SettingsDemo.exe.config file zoals hieronder weergegeven (gedeelte van bestand):
name="SettingsDemo.Settings"
type="System.Configuration.CustomSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
In SettingsDemo.exe.config is duidelijk te zien hoe de implementatie van ‘strong typing’ vertaald wordt naar een -groep waar verwezen wordt naar het type ‘System.Configuration.CustomSection’.
Mapped configurations
Er bestaan verschillende methods in de ConfigurationManager-klasse om configuraties te openen, waaronder de OpenExeConfiguration maar ook een OpenMachineConfiguration om een machine.config file te openen. Bovendien bestaan beide methods ook in een ‘Mapped’ uitvoering. In de mapped versie van deze methods kan (moet) een pad en bestandsnaam worden opgegeven naar het .config-bestand dat moet worden ingelezen. Een mogelijke toepassing daarvan is een universele tool om configuraties uit te lezen. Via de OpenMappedExeConfiguration-method kan vervolgens elke configuratie ingelezen worden, en zelfs aangepast worden opgeslagen.
Dim config As Configuration = _
ConfigurationManager.OpenMappedExeConfiguration(_
"TestApp.exe.config", _
ConfigurationUserLevel.None)
Project settings
In de projecteigenschappen in Visual Studio zijn via een user interface ook ‘Project settings’ op te geven. Die worden standaard niet opgeslagen in een configuratie-bestand, tot het moment dat de Save-method wordt aangeroepen. Een setting ‘WordOfWelcome’ met als waarde ‘HelloWorld’ resulteert in onderstaande extra regels in het configuratiebestand na een Save()-opdracht:
HelloWorld
Bovenstaande code illustreert de Save-method die alle in de applicatie aanwezige instellingen opslaat, ongeacht of daar nou expliciet gedefinieerde klasses voor bestaan of niet.
Instellingen zoals de originele URL van Web References worden door Visual Studio ook opgeslagen in de project-settings en dus in het App.Config-file, ook in ‘Class only’ projecten waarbij er dus nooit een app.exe.Config bestaat. Op basis van deze instellingen kan later voor ‘Update Web Reference’ gekozen worden.
Het heeft dus geen zin om instellingen op te slaan in dit bestand vanuit ClickOnce-applicaties
ClickOnce
Wie al serieus gewerkt heeft met ClickOnce-applicaties, heeft gemerkt dat het app.exe.Config-file bij veranderingen op de server altijd meegestuurd wordt naar de client. Het heeft dus geen zin om instellingen op te slaan in dit bestand vanuit ClickOnce-applicaties. Microsoft kiest er bij ClickOnce voor om dit bestand te laten gebruiken voor applicatie ‘defaults’. Aangepaste instellingen moeten worden opgeslagen in het user.Config-bestand. Op het moment dat er een applicatie-update wordt geïnstalleerd, zal het ClickOnce-systeem een merge uitvoeren op de oude .Config-files (zowel user als application!) en het resultaat omzetten naar een nieuwe applicatie-directory.
Door ConfigurationUserLevel.PerUserRoamingAndLocal of ConfigurationUserLevel.PerUserRoaming te gebruiken als parameter bij het openen van een configuratie zal user.Config geopend worden en worden aanpassingen daarin opgeslagen na een Save() commando
Meer informatie over ClickOnce in combinatie met applicationsetting-file is te vinden op http://msdn2.microsoft.com/en-us/library/ms135488.
Samenvatting
Visual Studio 2005 in combinatie met .Net 2.0 biedt een behoorlijke vooruitgang op het gebied van het ophalen, aanpassen en weer wegschrijven van instellingen. Er dient wel het nodige aan voorbereiding te gebeuren in de vorm van het schrijven van een eigen klasse waarin de sectionGroups en sections worden gedefinieerd, maar het toepassen van deze technologie leidt vervolgens wel weer tot een vermindering van potentiële bugs in je end-user applicaties.
Dit artikel is gebaseerd op een Win32-applicatie. Het gebruik maken van settings vanuit een webapplicatie op basis van dit artikel is ook mogelijk, maar dan moet het ASP.Net process account wel voldoende rechten hebben m.b.t. de verschillende .Config-files. Impersonation is de meest eenvoudige manier om dat te regelen. Activeer daarvoor ‘Integrated Windows Authentication’ en geef in de Web.Config de mogelijkheid vrij om impersonation toe te passen.
Speciale dank gaat uit naar de website http://www.kamalpatel.net/ConvertCSharp2VB.aspx waar zich een online converter bevindt om C#-code om te zetten naar Vb.Net. Mijn eigen code had ik in C# ontwikkeld, maar voor dit artikel was Vb.Net gewenst.