Imports System.IO Imports System.Reflection Imports System.Xml.Serialization Imports WinLineArtikelnummerGenerator.ConfigAttributes Public Class ConfigManager(Of T) Private Const USER_CONFIG_NAME As String = "UserConfig.xml" Private Const COMPUTER_CONFIG_NAME As String = "ComputerConfig.xml" Private Const APP_CONFIG_NAME As String = "AppConfig.xml" Private ReadOnly _LogConfig As LogConfig Private ReadOnly _Logger As Logger Private ReadOnly _File As File Private ReadOnly _UserDirectory As String Private ReadOnly _UserConfigPath As String Private ReadOnly _ComputerDirectory As String Private ReadOnly _ComputerConfigPath As String Private ReadOnly _AppConfigPath As String Private ReadOnly _AppConfigDirectory As String Private ReadOnly _TestMode As Boolean = False Private ReadOnly _Blueprint As T Private ReadOnly _BlueprintType As Type Private ReadOnly _Serializer As XmlSerializer Private ReadOnly _ExcludedAttributes = New List(Of Type) From { GetType(ConnectionStringAttribute), GetType(ConnectionStringTestAttribute), GetType(GlobalSettingAttribute) } Private _WriteAllValuesToUserConfig As Boolean = False ''' ''' Returns the currently loaded config object ''' ''' Public ReadOnly Property Config As T ''' ''' Path to the current user config. ''' ''' Public ReadOnly Property UserConfigPath As String Get Return _UserConfigPath End Get End Property ''' ''' Path to the current computer config. Maybe the same as `UserConfigPath` ''' ''' Public ReadOnly Property ComputerConfigPath As String Get Return _ComputerConfigPath End Get End Property Public ReadOnly Property AppConfigPath As String Get Return _AppConfigPath End Get End Property ''' ''' Creates a new instance of the ConfigManager ''' ''' ''' LogConfig instance ''' The path to check for a user config file, eg. AppData (Usually Application.UserAppDataPath or Application.LocalUserAppDataPath) ''' The path to check for a computer config file, eg. ProgramData (Usually Application.CommonAppDataPath) ''' The path to check for a third config file. This is useful when running the Application in an environment where AppData/ProgramData directories are not available ''' Override values from ComputerConfig with UserConfig Public Sub New(LogConfig As LogConfig, UserConfigPath As String, ComputerConfigPath As String, Optional ApplicationStartupPath As String = "", Optional ForceUserConfig As Boolean = False) _LogConfig = LogConfig _Logger = LogConfig.GetLogger() _File = New File(_LogConfig) _Blueprint = Activator.CreateInstance(Of T) _BlueprintType = _Blueprint.GetType _Serializer = New XmlSerializer(_BlueprintType) _UserDirectory = _File.CreateDirectory(UserConfigPath) _UserConfigPath = Path.Combine(_UserDirectory, USER_CONFIG_NAME) If ComputerConfigPath <> String.Empty Then _ComputerDirectory = _File.CreateDirectory(ComputerConfigPath) _ComputerConfigPath = Path.Combine(_ComputerDirectory, COMPUTER_CONFIG_NAME) End If If ApplicationStartupPath <> String.Empty Then _AppConfigPath = Path.Combine(ApplicationStartupPath, APP_CONFIG_NAME) End If _WriteAllValuesToUserConfig = ForceUserConfig Config = LoadConfig() End Sub ''' ''' Creates a new ConfigManager with a single (user)config path ''' ''' LogConfig instance ''' The path to check for a user config file, eg. AppData (Usually Application.UserAppDataPath or Application.LocalUserAppDataPath) Public Sub New(LogConfig As LogConfig, ConfigPath As String) MyClass.New(LogConfig, ConfigPath, String.Empty, String.Empty, ForceUserConfig:=True) End Sub ''' ''' Save the current config object to `UserConfigPath` ''' ''' Force saving all attributes including the attributes marked as excluded ''' True if save was successful, False otherwise Public Function Save(Optional ForceAll As Boolean = False) As Boolean Try WriteToFile(Config, _UserConfigPath, ForceAll) Return True Catch ex As Exception _Logger.Error(ex) Return False End Try End Function ''' ''' Copies all properties from Source to Target, except those who have an attribute ''' listed in ExcludedAttributeTypes ''' ''' Source config object ''' Target config object ''' List of Attribute type to exclude Private Sub CopyValues(Source As T, Target As T, Optional ExcludedAttributeTypes As List(Of Type) = Nothing) Dim oType As Type = GetType(T) Dim oExcludedAttributeTypes = IIf(IsNothing(ExcludedAttributeTypes), New List(Of Type), ExcludedAttributeTypes) Dim oProperties = oType.GetProperties(). Where(Function(p) Return p.CanRead And p.CanWrite End Function). Where(Function(p) For Each oAttributeType As Type In oExcludedAttributeTypes If Attribute.IsDefined(p, oAttributeType) Then Return False End If Next Return True End Function) For Each oProperty As PropertyInfo In oProperties Dim oValue = oProperty.GetValue(Source, Nothing) If Not IsNothing(oValue) Then oProperty.SetValue(Target, oValue, Nothing) End If Next End Sub ''' ''' Filters a config object by copying all values except `ExcludedAttributeTypes` ''' ''' Config object ''' List of Attribute type to exclude ''' Private Function FilterValues(ByVal Data As T, ExcludedAttributeTypes As List(Of Type)) As T Dim oResult As T = Activator.CreateInstance(Of T) CopyValues(Data, oResult, ExcludedAttributeTypes) Return oResult End Function Private Function LoadConfig() As T ' first create an empty/default config object Dim oConfig = Activator.CreateInstance(_BlueprintType) ' try to load the special app config oConfig = LoadAppConfig(oConfig) ' try to load the computer config oConfig = LoadComputerConfig(oConfig) ' now try to load userconfig oConfig = LoadUserConfig(oConfig) Return oConfig End Function Private Function LoadAppConfig(ByVal Config As T) As T If Not String.IsNullOrEmpty(_AppConfigPath) AndAlso System.IO.File.Exists(_AppConfigPath) Then Try Dim oAppConfig = ReadFromFile(_AppConfigPath) CopyValues(oAppConfig, Config) Catch ex As Exception _Logger.Error(ex) _Logger.Warn("ApplicationConfig could not be loaded!") End Try _WriteAllValuesToUserConfig = False Else _Logger.Debug("ApplicationConfig does not exist.") _WriteAllValuesToUserConfig = True End If Return Config End Function Private Function LoadComputerConfig(ByVal Config As T) As T If System.IO.File.Exists(_ComputerConfigPath) Then Try Dim oComputerConfig = ReadFromFile(_ComputerConfigPath) CopyValues(oComputerConfig, Config) Catch ex As Exception _Logger.Error(ex) _Logger.Warn("Computer config could not be loaded!") End Try _WriteAllValuesToUserConfig = False Else _Logger.Debug("Computer config does not exist.") _WriteAllValuesToUserConfig = True End If Return Config End Function Private Function LoadUserConfig(ByVal Config As T) As T If System.IO.File.Exists(_UserConfigPath) Then Try Dim oUserConfig = ReadFromFile(_UserConfigPath) ' if user config exists If Not IsNothing(oUserConfig) Then Dim oExcludedAttributes As New List(Of Type) ' Copy values from user config to final config If _WriteAllValuesToUserConfig Then CopyValues(oUserConfig, Config, New List(Of Type)) Else CopyValues(oUserConfig, Config, _ExcludedAttributes) End If End If Catch ex As Exception _Logger.Error(ex) _Logger.Warn("User config could not be loaded!") End Try Else _Logger.Debug("User config does not exist.") End If Return Config End Function Private Function TestHasAttribute(Config As T, AttributeType As Type) As Boolean For Each oProperty As PropertyInfo In Config.GetType.GetProperties() If Attribute.IsDefined(oProperty, GetType(ConnectionStringAttribute)) Then Return True End If Next Return False End Function ''' ''' Serialize a config object to byte array ''' ''' ''' Private Function Serialize(Data As T) As Byte() Try _Logger.Debug("Serializing config object") Using oStream = New MemoryStream() _Serializer.Serialize(oStream, Data) Return oStream.ToArray() End Using Catch ex As Exception _Logger.Error(ex) Throw ex End Try End Function ''' ''' Write an object to disk as xml ''' ''' The object to write ''' The file name to write to Private Sub WriteToFile(Data As T, Path As String, ForceAll As Boolean) Try _Logger.Debug("Saving config to: {0}", Path) ' If config was loaded from computer config, ' DO NOT save connection string, etc. to user config If _WriteAllValuesToUserConfig = False And ForceAll = False Then Data = FilterValues(Data, _ExcludedAttributes) End If Dim oBytes = Serialize(Data) Using oFileStream = New FileStream(Path, FileMode.Create, FileAccess.Write) oFileStream.Write(oBytes, 0, oBytes.Length) oFileStream.Flush() End Using Catch ex As Exception _Logger.Warn("Could not save config to {0}", Path) _Logger.Error(ex) Throw ex End Try End Sub ''' ''' Reads an xml from disk and deserializes to object ''' ''' Private Function ReadFromFile(Path As String) As T Try _Logger.Debug("Loading config from: {0}", Path) Dim oConfig As T Using oReader As New StreamReader(Path) oConfig = _Serializer.Deserialize(oReader) End Using ' If oConfig is Nothing, a config file was created but nothing was written to it. ' In this case we need to create oConfig from defaults so we have at least some config object If oConfig Is Nothing Then _Logger.Debug("Config file is valid but empty. Loading default values") oConfig = Activator.CreateInstance(_BlueprintType) End If Return oConfig Catch ex As Exception _Logger.Warn("Could not load config from {0}", Path) _Logger.Error(ex) Throw ex End Try End Function End Class