Die Entkopplung von den Komponenten einer Anwendung wird in letzter Zeit sehr groß geschrieben. Es reicht aber nicht aus, die Klasse hinter einem Interface zu verstecken. Es ist viel wichtiger, die Logik der Komponente vor dem Rest der Anwendung zu verbergen. Dieses möchte ich Euch im ersten Teil anhand einer Lizenzverwaltung zeigen. Im zweiten Teil nehme ich die Einstellungen und eine Rechteverwaltung als Beispiel.
Eine Lizenzverwaltung kann folgende Ansprüche haben:
Standard Lizenzen, Kundenspezifische Lizenzen, Lizenzpakete, unterschiedliche Kombinationen der Lizenzen in Paketen, Begrenzung der Laufzeit nach Tagen oder einem festen Datum.
Werden diese Möglichkeiten in die Anwendung weitergereicht, ist die Flexibilität und die Wartbarkeit schnell eingeschränkt. Bei genauerer Betrachtung benötigt die Anwendung nur die Information, ob eine Funktionalität frei geschaltet ist. Wie die Lizenzverwaltung diesen Wert ermittelt, ist ihre private Sache. In den meisten Fällen wird der Wert ein Boolean sein, aber auch andere Typen sind sinnvoll, z.B. ein Integer für die Anzahl der erlaubten Benutzer.
Meine Lösung:
Mit Attributen werden die Lizenzen (jeweils eine Funktionalität) bekannt gegeben. Zu jeder Lizenz gehören eine Id, ein Typ und ein Default. Die Anwendung kann die Lizenzverwaltung mit der Id nach dem dazugehörigen Wert fragen und entsprechend Menüs, Dialoge, usw. aktivieren. Meine Beispiellösung der Lizenzverwaltung liest aus einem Verzeichnis Lizenz-Pakete (XML Dateien). In diesen Dateien stehen die einzelnen Lizenzen (Id und Wert) und deren Gültigkeiten. Wird eine Id in den Dateien nicht gefunden, wird der Default zurückgegeben. Diese Dateien sind in meinem Beispiel (fürs Testen) nicht verschlüsselt, für eine Auslieferung wäre das aber sinnvoll.
Zuerst müssen die Lizenzen definiert werden. Hier bieten sich Attribute an der Assembly (in der Assemblyinfo) an. Hier zeigt sich leider eine Schwäche der Attribute: Als Werte können nur einfache Datentypen (Zahlen und Texte) übergeben werden, Konstruktoren (new Guid(?)) und Variablen (myLicences.LicenceNr1) können nicht angegeben. Ich habe mich deshalb entschlossen, die Lizenzen als Member in eine Klasse zu definieren. So kann man die Lizenz mit dem aussagekräftigen Variablennamen ansprechen.
[ConfigurationClass] public class AppLicenses { [License(typeof(bool), Name="Control sichtbar", Description="macht ein Control sichtbar", Default=true)] public static Guid ControlVisible = new Guid("{84EEC6D9-A0C1-47d7-963F-B8A6913EDB02}"); } |
Die Verwendung ist genauso einfach. Die Lizenzverwaltung wird durch eine Classfactory erzeugt. Die Lizenzverwaltung fragt man mit der Id (aus der Membervariablen) nach dem Wert. Mit diesem lassen sich nun Menüpunkte aktivieren oder Funktionen steuern.
ILicenses Licenses = ClassFactory.GetObject<ILicenses>(); if (Licenses != null) txtTest.Visible = Licenses.GetLicense<bool>(AppLicenses.ControlVisible); |
Das ist alles, was die Anwendung über Lizenzen wissen muss. Der Rest wird in der Lizenzverwaltung implementiert. Im ersten Schritt sucht die Verwaltung alle Assemblies nach Lizenzdefinitionen durch und speichert sie in einem Dictionary.
foreach (Type aClass in assembly.GetTypes()) { // erst die Klassen finden ... if (aClass.GetCustomAttributes(typeof(ConfigurationClassAttribute), true).Length>0) { object aObject = Activator.CreateInstance(aClass); // ... dann die Member lesen ... foreach (FieldInfo member in aClass.GetFields()) { // ... die die Attribute haben ... LicenseAttribute [] classAttributes = (LicenseAttribute []) member.GetCustomAttributes(typeof(LicenseAttribute), true); if (classAttributes!=null && classAttributes.Length == 1) { // ... und den Wert speichern Guid Id = (Guid) member.GetValue(aObject); DefaultConfigurationList.Add(Id, classAttributes[0]); } } } } |
Im zweiten Schritt werden alle XML Dateien im Licence Verzeichnis gelesen.
// erst mal das Verzeichnis suchen string AppPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); string LicensePath = Path.Combine(AppPath, "License"); DirectoryInfo dir = new DirectoryInfo(LicensePath); // dann alle XML Dateien lesen FileInfo[] XmlFiles = dir.GetFiles("*.xml"); foreach (FileInfo XmlFile in XmlFiles) { // Jede Datei lesen StreamReader reader = File.OpenText(XmlFile.FullName); XmlDocument Doc = new XmlDocument(); Doc.Load(reader); // jetzt durch die Knoten wandern ... foreach (XmlNode Licenses in Doc.GetElementsByTagName("licenses")) { foreach (XmlNode License in Doc.GetElementsByTagName("license")) { // ... bis man die Lizenzen gefunden hat string sId = ""; sId = License.Attributes["id"].Value; string Value = License.Attributes["value"].Value; Guid Id = new Guid(sId); // Den Wert von Text nach Object wandeln und speichern ValueList.Add(Id, Value==?TRUE?) // hier nur Boolean } } } |
Bei einer Anfrage wird der Wert aus der XML Datei zurückgegeben. Wenn keiner gefunden wurde, wird der Default Wert zurückgegeben.
if (ValueList.ContainsKey(Id)) return ValueList[Id]; return DefaultConfigurationList[Id].Default; |
Der ganze Code und ein kleines Beispiel steht auf meiner Homepage www.KlaasWedemeyer.de