Für einen Response-Filter, der den HTML-Code vor der Ausgabe noch einmal gezielt verändern kann, habe ich eine Lösung gesucht, die dafür notwendigen Informationen zentral abzulegen. Nahe liegend ist da natürlich die Verwendung der ASP.NET-eigenen web.config. In diesem Fall genügten mir aber die einfachen Key/Value-Paarungen des Abschnittes AppSettings nicht, da ich mindestens 4 Konfigurationswerte unterbringen wollte, und dies auch noch mehrfach:
- pageName
- findString
- newString
- type
Die Lösung liegt in der Verwendung der Klasse ConfigurationSection, mit der man relativ einfach eigene Konfigurationsabschnittstypen implementieren kann. In einer Klasse, die von ConfigurationSection ableitet, werden im Grunde die einzelnen Eigenschaften (Properties) definiert, die später in der web.config konfigurierbar sind und per Code über den ConfigurationManager abrufbar werden.
Einfaches Konfigurationselement
Fangen wir mit dem Wurzelelement an, das zusätzlich ein Attribut namens active erhalten soll, mit dem man später den ResponseFilter einfach ein- und ausschalten kann:
'*** Definition des Konfigurationsabschnitts
Public Class ResponseFilterConfiguration
Inherits ConfigurationSection
<ConfigurationProperty("active", DefaultValue:="True", IsRequired:=True)> _
Public ReadOnly Property Active() As Boolean
Get
Return CType(MyBase.Item("active"), Boolean)
End Get
End Property
End Class
Jedes ConfigurationProperty besitzt unter anderem folgende wichtige Attribute:
| Attribut | Beschreibung |
| Name | Name der Eigenschaft (erforderlich) |
| DefaultValue | Standardwert der Eigenschaft |
| IsRequired | Ist das Element erforderlich? |
| IsKey | Ist der Inhalt des Element ein eindeutiger Schlüssel? |
In der web.config muss das neue Konfigurationselement nun noch bekannt gemacht werden, um es anschließend konfigurieren zu können:
<configuration>
<configSections>
<section name="responseFilterSettings" type="ResponseFilterConfiguration"/>
</configSections>
<responseFilterSettings active="False"></responseFilterSettings>
...
<configuration>
Der Zugriff auf die Einstellung per Code findet über den ConfigurationManager statt:
Dim rfc As ResponseFilterConfiguration = _
ConfigurationManager.GetSection("ResponseFilterSettings")
Response.Write(rfc.Active)
Auflistung mehrerer Konfigurationsunterelemente
In meinem Fall war das allerdings erst die halbe Miete, denn ich brauchte unterhalb des Wurzelelements ResponseFilterSettings ja noch eine beliebige Anzahl an Unterelementen. Hierfür stellt das .NET-Framework die Klasse ConfigurationElementCollection bereit, mit der mehrere zusammengehörige Eigenschaften als Liste abgebildet werden können.
Zunächst die Definition der einzelnen Eigenschaften, die in einer eigenen Klasse abgebildet sind, die diesmal von ConfigurationElement erben muss:
'*** Definition der Konfigurationsunterelemente
Public Class ResponseFilterConfigurationElement
Inherits ConfigurationElement
<ConfigurationProperty("name", IsRequired:=True, IsKey:=True)> _
Public ReadOnly Property Name() As String
Get
Return CType(MyBase.Item("name"), String)
End Get
End Property
<ConfigurationProperty("pageName", IsRequired:=True)> _
Public ReadOnly Property PageName() As String
Get
Return CType(MyBase.Item("pageName"), String)
End Get
End Property
<ConfigurationProperty("findString", IsRequired:=True)> _
Public ReadOnly Property FindString() As String
Get
Return CType(MyBase.Item("findString"), String)
End Get
End Property
<ConfigurationProperty("newString", IsRequired:=True)> _
Public ReadOnly Property NewString() As String
Get
Return CType(MyBase.Item("newString"), String)
End Get
End Property
<ConfigurationProperty("type", IsRequired:=True)> _
Public ReadOnly Property Type() As ResponseFilterManipulation
Get
Return CType(MyBase.Item("type"), ResponseFilterManipulation)
End Get
End Property
End Class
Man beachte, daß zumindest eine eindeutige Eigenschaft definiert sein muss, um später über die Collection auf einen Eintrag zugreifen zu können. Hier ist das, wie in den meisten Fällen, Name. Der Datentyp der Eigenschaft Type verweist auf Werte einer Enumeration namens ResponseFilterManipulation, die außerhalb der Klasse definiert ist.
Public Enum ResponseFilterManipulation
InsertBefore = 0
InsertAfter = 1
Replace = 2
End Enum
Nun zur Definition der Auflistung, wiederum in einer eigenen Klasse, die nun von ConfigurationElementCollection erbt, wobei die beiden Standardmethoden CreateNewElement (Erstellung eines neuen ConfigurationElements) und GetElementKey (Abrufen eines ConfigurationElements) überschrieben werden müssen. Zusätzlich braucht es eine Eigenschaft Item, um ein einzelnes Unterelement abrufen zu können:
'*** Auflistung des Konfigurationsunterelemente
Public Class ResponseFilterConfigurationElementCollection
Inherits ConfigurationElementCollection
Protected Overloads Overrides Function CreateNewElement( _
) As System.Configuration.ConfigurationElement
Return New ResponseFilterConfigurationElement
End Function
Protected Overrides Function GetElementKey( _
ByVal element As System.Configuration.ConfigurationElement) As Object
Return CType(element, ResponseFilterConfigurationElement).Name
End Function
Public Shadows Property Item( _
ByVal index As Integer) As ResponseFilterConfigurationElement
Get
Return MyBase.BaseGet(index)
End Get
Set(ByVal value As ResponseFilterConfigurationElement)
If (Not (MyBase.BaseGet(index)) Is Nothing) Then
MyBase.BaseRemoveAt(index)
End If
Me.BaseAdd(index, value)
End Set
End Property
End Class
Wie man eine solche Auflistung generisch realisieren kann, zeigt Albert Weinert in seinem Artikel Generische ConfigurationElementCollection.
Bleibt noch eine Eigenschaft in der zu Beginn erstellen Hauptklasse ResponseFilterConfiguration anzulegen, um auf die Unterelemente zugreifen zu können. Um sich die Definition einer eigenen GetSection-Methode zu sparen, wird einfach das Attribut IsDefaultCollection der Property gesetzt:
...
<ConfigurationProperty("responseFilter", IsRequired:=True, _
IsDefaultCollection:=True)> _
Public ReadOnly Property ResponseFilter( _
) As ResponseFilterConfigurationElementCollection
Get
Return CType(MyBase.Item("responseFilter"), _
ResponseFilterConfigurationElementCollection)
End Get
End Property
...
Der web.config könnte dann zum Beispiel so aussehen:
<responseFilterSettings active="True">
<responseFilter>
<add name="Test1"
pageName="test.aspx"
findString="<body"
newString="<body class="test""
type="Replace" />
<add name="Test2"
pageName="test.aspx"
findString="</head>"
newString="<link rel="stylesheet" href="/test.css" type="text/css" />"
type="InsertBefore" />
</responseFilter>
</responseFilterSettings>
(In diesem Beispiel ist darauf zu achten, dass die Werte innerhalt eines Attributs keine spitzen Klammern enthalten dürfen. Sie müssen durch die entsprechenden HTML-Entities ersetzt werden.)
Im übrigen Code können nun die einzelnen Einträge sehr einfach durchlaufen werden:
Dim rfc As ResponseFilterConfiguration = _
ConfigurationManager.GetSection("responseFilterSettings")
For Each rf As ResponseFilterConfigurationElement In rfc.ResponseFilter
...
Next
Download: Beispielprojekt (CustomConfigurationSections.zip)