I've noticed that Microsoft has implemented a CssTextWriter which is internal
internal sealed class CssTextWriter : TextWriter
{
....
}
Is there a Css writer for .net that?
For example I would like to write code such as:
CssTextWriter writer = new CssTextWriter(textWriter);
writer.WriteBeginCssRule("p");
writer.WriteAttribute("font-family", "Arial,Liberation Sans,DejaVu Sans,sans-serif");
writer.WriteEndCssRule();
The above code would output to the stream as follows:
p { font-family: Arial,Liberation Sans,DejaVu Sans,sans-serif; }
dotLess
http://www.dotlesscss.org/ looks like it would do the job, but was a bit much I just needed a single clss
I wrapped calls to the internal Microsoft class (yes naughty and it might go away in future releases of .net etc....)
public class CssTextWriter
{
public CssTextWriter(TextWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
this.Writer = writer;
this.Initialize();
}
/// <summary>
/// Gets the writer.
/// </summary>
/// <value>
/// The writer.
/// </value>
public TextWriter Writer { get; private set; }
/// <summary>
/// Gets or sets the internal CSS text writer.
/// </summary>
/// <value>
/// The internal CSS text writer.
/// </value>
private object InternalCssTextWriter
{
get;
set;
}
public void WriteBeginCssRule(string selector)
{
this.InternalCssTextWriter.InvokeMethod("WriteBeginCssRule", new[] { selector });
}
public void WriteEndCssRule()
{
this.InternalCssTextWriter.InvokeMethod("WriteEndCssRule");
}
public void WriteAttribute(string name, string value)
{
this.InternalCssTextWriter.InvokeMethod("WriteAttribute", new[] { name, value }, new Type[] { typeof(string), typeof(string) });
}
public void Write(string value)
{
this.InternalCssTextWriter.InvokeMethod("Write", new[] { value }, new Type[] { typeof(string) });
}
public void WriteAttribute(HtmlTextWriterStyle key, string value)
{
this.InternalCssTextWriter.InvokeMethod("WriteAttribute", new object[] { key, value }, new Type[] { typeof(HtmlTextWriterStyle), typeof(string) });
}
private void Initialize()
{
Type internalType = typeof(System.Web.UI.HtmlTextWriter).Assembly.GetType("System.Web.UI.CssTextWriter");
ConstructorInfo ctor = internalType.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0];
this.InternalCssTextWriter = ctor.Invoke(new[] { this.Writer });
}
}
Related
Coding in C#, I am blocked on a silly JSON deserialization and hoping your expert eyes will find what I'm missing.
I am trying to extract the following JSON file:
{
"Document":
{
"Version": "1.0.0",
"LastUpdate": "10.08.2020",
"Description": "This document enumarates manufactuer capabilities",
"Id": ""
},
"Capacities": [
{
"Id": 0,
"Description": "",
"Enable": true,
"RelativePath": "Company1\\manifest.json"
},
{
"Id": 1,
"Description": "",
"Enable": true,
"RelativePath": "Company2\\manifest.json"
}
]
}
The ManifestDescriptor class should self-extract (and store) JSON data through its constructor call. The implementation is as follow:
using JsonTools;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using SessionService;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace BaseService.Manifest
{
public class ManifestDescriptor : JObject
{
/// <summary>
/// Contains Json document descriptor data
/// </summary>
private DocSection _document;
private List<Capacity> _capacity;
/// <summary>
/// Contains Json document descriptor data
/// </summary>
[JsonProperty("Document")]
public DocSection Document
{
get => _document;
set
{
if (value != _document)
{
_document = value;
}
}
}
/// <summary>
/// Contains document capacities
/// </summary>
[JsonProperty("Capacities")]
public List<Capacity> Capacities
{
get => _capacity;
internal set
{
if (value != _capacity)
{
_capacity = value;
}
}
}
public ManifestDescriptor(string absolutePath)
{
string l_rawJson = string.Empty;
if (File.Exists(absolutePath))
{
_fileInfo = new FileInfo(absolutePath);
if (!JsonParser.LoadJsonFile(absolutePath, ref l_rawJson))
{
Log.Error("Could not extract Json file data.");
return;
}
// Populate current class with manifest data
JsonConvert.PopulateObject(l_rawJson, this);
}
else Log.Warning("Given manifest file do not exist: \n\r {0}", absolutePath);
}
}
}
ManifestDescriptor contains a Document property implemented as follow:
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
namespace BaseService.Manifest
{
/// <summary>
/// Class container giving Json document version & its utility
/// </summary>
public class DocSection
{
private Version _version;
private DateTime _lastUpdate;
private string _description;
private string _id;
private string _class;
/// <summary>
/// Contains document version
/// </summary>
[JsonProperty("Version")]
public Version Version
{
get => _version;
internal set
{
if (value != _version)
{
_version = value;
}
}
}
/// <summary>
/// Contains last update document version
/// </summary>
[JsonProperty("LastUpdate")]
public DateTime LastUpdate
{
get => _lastUpdate;
internal set
{
if(value != _lastUpdate)
{
_lastUpdate = value;
}
}
}
/// <summary>
/// Contains document description
/// </summary>
[JsonProperty("Description")]
public string Description
{
get => _description;
internal set
{
if(value != _description)
{
_description = value;
}
}
}
/// <summary>
/// Contains document ID
/// </summary>
[JsonProperty("Id")]
public string Id
{
get => _id;
internal set
{
if (value != _id)
{
_id = value;
}
}
}
/// <summary>
/// Object class type to instanciate regarding capacity data of the document
/// </summary>
[JsonIgnore]
[JsonProperty("Class")]
public string Class
{
get => _class;
internal set
{
if(value != _class)
{
_class = value;
}
}
}
}
}
However when reaching the line JsonConvert.PopulateObject(l_rawJson, this); called in ManifestDescriptor class constructor, I am reaching the following execption:
Cannot populate JSON object onto type
'BaseService.Manifest.ManifestDescriptor'. Path 'Document', line 2,
position 13."
May you have an idea of the issue?
Thanks for your attention,
As per #SelimYildiz, you really need to separate the population of objetc outside of the object you are trying to serialize.
If your intention is to serialize your ManifestDescriptor without knowing the process of it all you need is to past your json. Then you can create a wrapper class that will do the thing.
What is your intention of making the ManifestDescriptor inherit a JObject?
This might give you a hint, for this sample demo I didn't inherit the JObject and instead of JsonConvert.PopulateObject I used JsonConvert.Deserialized.
Wrapper Class
public class ParserWrapper<T>
where T : new()
{
private FileInfo _fileInfo;
public T ParsedObject { get; set; }
public ParserWrapper(string absolutePath)
{
string l_rawJson = string.Empty;
if (File.Exists(absolutePath))
{
_fileInfo = new FileInfo(absolutePath);
if (!JsonParser.LoadJsonFile(absolutePath, ref l_rawJson))
{
System.Diagnostics.Debug.Write("Could not extract Json file data.");
return;
}
// Populate current class with manifest data
ParsedObject = JsonConvert.DeserializeObject<T>(l_rawJson);
}
else System.Diagnostics.Debug.Write("Given manifest file do not exist: \n\r {0}", absolutePath);
}
}
ManifestorDescriptor
public class ManifestDescriptor
{
private DocSection _document;
private List<Capacity> _capacity;
[JsonProperty("Document")]
public DocSection Document
{
get => _document;
set
{
if (value != _document)
{
_document = value;
}
}
}
[JsonProperty("Capacities")]
public List<Capacity> Capacities
{
get => _capacity;
internal set
{
if (value != _capacity)
{
_capacity = value;
}
}
}
}
Implementation
var obj = new ParserWrapper<ManifestDescriptor>("json1.json");
Output:
Thanks for your attention. Combined comments help me to find out the issue. Coming back from #Rahul Sharma code answer and drawing back towards my original code, I could detect the two following issues:
Setter should not be declared as internal otherwise JsonConvert.PopulateObject(...) function will end with a nullable object.
I've set ManifestDescriptor class as child of Jobject. Removing the parent seems to resolve the raised exception I get.
The working code is then:
ManifestDescriptor class:
public class ManifestDescriptor
{
/// <summary>
/// Manifest file information field
/// </summary>
private FileInfo _fileInfo;
/// <summary>
/// Contains Json document descriptor data
/// </summary>
private DocSection _document;
private List<Capacity> _capacity;
/// <summary>
/// Manifest file information
/// </summary>
public FileInfo FileInfo
{
get => _fileInfo;
set
{
if(value != _fileInfo)
_fileInfo = value;
}
}
/// <summary>
/// Contains Json document descriptor data
/// </summary>
[JsonProperty("Document")]
public DocSection Document
{
get => _document;
set
{
if (value != _document)
{
_document = value;
}
}
}
/// <summary>
/// Contains document capacities
/// </summary>
[JsonProperty("Capacities")]
public List<Capacity> Capacities
{
get => _capacity;
internal set
{
if (value != _capacity)
{
_capacity = value;
}
}
}
public ManifestDescriptor(string absolutePath)
{
string l_rawJson = string.Empty;
if (File.Exists(absolutePath))
{
_fileInfo = new FileInfo(absolutePath);
if (!JsonParser.LoadJsonFile(absolutePath, ref l_rawJson))
{
Log.Error("Could not extract Json file data.");
return;
}
// Populate current class with manifest data
JsonConvert.PopulateObject(l_rawJson, this);
}
else Log.Warning("Given manifest file do not exist: \n\r {0}", absolutePath);
}
}
DocSection class (Document property):
/// <summary>
/// Class container giving Json document version & its utility
/// </summary>
public class DocSection
{
private Version _version;
private DateTime _lastUpdate;
private string _description;
private string _id;
private string _class;
/// <summary>
/// Contains document version
/// </summary>
[JsonProperty("Version")]
public Version Version
{
get => _version;
set
{
if (value != _version)
{
_version = value;
}
}
}
/// <summary>
/// Contains last update document version
/// </summary>
[JsonProperty("LastUpdate")]
public DateTime LastUpdate
{
get => _lastUpdate;
set
{
if(value != _lastUpdate)
{
_lastUpdate = value;
}
}
}
/// <summary>
/// Contains document description
/// </summary>
[JsonProperty("Description")]
public string Description
{
get => _description;
set
{
if(value != _description)
{
_description = value;
}
}
}
/// <summary>
/// Contains document ID
/// </summary>
[JsonProperty("Id")]
public string Id
{
get => _id;
internal set
{
if (value != _id)
{
_id = value;
}
}
}
}
I have a class LanguagePopupMessage which is used all over the application (and external libraries). If this class is constructed it fetches the namespace where it's created and adds a suffix to be unique.
The Question is: How can get all LanguagePopupMessage definitions including the fieldname parameter?
Im using structuremap in my application. It's also scanning all libraries at startup, so maybe there is a possiblity how to automaticate it. 👀
using System;
using System.Diagnostics;
namespace ConsoleApp1
{
/// <summary>
/// Creates the namespace for a popup window and has an additional flag for the caption
/// </summary>
public class LanguagePopupMessage
{
public string Namespace { get; }
public string Caption => $"{Namespace}Caption";
public LanguagePopupMessage(string fieldName)
{
if(string.IsNullOrEmpty(fieldName))
throw new ArgumentNullException(nameof(fieldName));
if (_GetNamespace() is Type type)
{
Namespace = $"{type}.{fieldName}";
}
else
{
throw new InvalidOperationException("could not fetch the namespace");
}
}
private Type _GetNamespace()
{
StackTrace st = new StackTrace();
foreach (var sf in st.GetFrames())
{
var type = sf.GetMethod().DeclaringType;
if (type != GetType())
{
return type;
}
}
return null;
}
public override string ToString()
{
return $"Namespace '{Namespace}' Caption '{Caption}'";
}
}
class Program
{
//ConsoleApp1.Program.PopupMessage.ConfigNotLoaded
//ConsoleApp1.Program.PopupMessage.ConfigNotLoadedCaption
private static readonly LanguagePopupMessage _CONFIG_NOT_LOADED_POPUP_MESSAGE = new LanguagePopupMessage("ConfigNotLoaded");
static void Main(string[] args)
{
Console.ReadKey();
}
}
}
namespace ConsoleApp1.Subfolder
{
public class SubfolderClass
{
/// <summary>
/// ConsoleApp1.Subfolder.SubfolderClass.FooMessage
/// ConsoleApp1.Subfolder.SubfolderClass.FooMessageCaption
/// </summary>
public static readonly LanguagePopupMessage Message = new LanguagePopupMessage("FooMessage");
}
}
I made a custom IRegistrationConvention - FindAllLanguagePopupMessages for structuremap. During runtime a new container scans all libraries -> all Types if there are any FieldInfo of type LanguagePopupMessage and adding it into a collection.
To get better performance, I made an Attribute - ContainsTranslationDefinition to filter the classes.
Sourcecode
public class ContainsTranslationDefinition : Attribute
{ }
/// <summary>
/// Creates the namespace for a popup window and has an additional flag for the caption
/// </summary>
public class LanguagePopupMessage
{
public string Namespace { get; }
public string Caption => $"{Namespace}Caption";
public LanguagePopupMessage(string fieldName)
{
if(string.IsNullOrEmpty(fieldName))
throw new ArgumentNullException(nameof(fieldName));
if (_GetNamespace() is Type type)
{
Namespace = $"{type}.{fieldName}";
}
else
{
throw new InvalidOperationException("could not fetch the namespace");
}
}
private Type _GetNamespace()
{
StackTrace st = new StackTrace();
foreach (var sf in st.GetFrames())
{
var type = sf.GetMethod().DeclaringType;
if (type != GetType())
{
return type;
}
}
return null;
}
public override string ToString()
{
return $"Namespace '{Namespace}' Caption '{Caption}'";
}
}
/// <summary>
/// Add <see cref="LanguagePopupMessage"/> into the <see cref="Container.Model"/> type lifecycle
/// </summary>
public class FindAllLanguagePopupMessages : IRegistrationConvention
{
private readonly ILifecycle _Lifecycle = new UniquePerRequestLifecycle();
public void ScanTypes(TypeSet types, Registry registry)
{
List<LanguagePopupMessage> dec = new List<LanguagePopupMessage>();
foreach (Type type in types.AllTypes())
{
if (!Attribute.IsDefined(type, typeof(ContainsTranslationDefinition)))
{
continue;
}
_FindConfigDeclarations(type, dec);
}
foreach (LanguagePopupMessage languagePopupMessage in dec)
{
Console.WriteLine($"{languagePopupMessage}");
}
}
private static void _FindConfigDeclarations(Type type, List<LanguagePopupMessage> declarations)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
declarations.AddRange(fields
.Where(info => info.IsInitOnly && typeof(LanguagePopupMessage).IsAssignableFrom(info.FieldType))
.Select(info => (LanguagePopupMessage)info.GetValue(null)));
// find all nested class types and run method recursively
foreach (var nestedType in type.GetNestedTypes(BindingFlags.Public))
{
_FindConfigDeclarations(nestedType, declarations);
}
}
}
[ContainsTranslationDefinition]
public class TestClass
{
private static readonly LanguagePopupMessage _CONFIG_1 = new LanguagePopupMessage("ConfigNotLoaded1");
private static readonly LanguagePopupMessage _CONFIG_2 = new LanguagePopupMessage("ConfigNotLoaded2");
}
[ContainsTranslationDefinition]
public class Program
{
//ConsoleApp1.Program.PopupMessage.ConfigNotLoaded
//ConsoleApp1.Program.PopupMessage.ConfigNotLoadedCaption
private static readonly LanguagePopupMessage _CONFIG_NOT_LOADED_POPUP_MESSAGE = new LanguagePopupMessage("ConfigNotLoaded3");
static void Main(string[] args)
{
// Create container and tell where to look for depencies
IContainer container = new Container(c => c.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
scanner.AssembliesFromApplicationBaseDirectory();
scanner.With(new FindAllLanguagePopupMessages());
}));
Console.ReadKey();
}
}
Preview
I have a requirement to create a nested configuration section. The issue is that I need to write an application that accesses any number of databases. These could be oracle, sql, or anything else... I want to make my config section look like this:
<connectionconfigurations>
<databaseConnection dbname="connection1" dbsourceConnect="connectionstring1" provider="sql">
<sqlQueries>
<add name="querynumber1"
sqlFilePath="C:\Users\me\Desktop\sql"/>
<add name="querynumber2"
sqlFilePath="C:\Users\me\Desktop\sql"/>
</sqlQueries>
</databaseConnection>
<databaseConnection dbname="connection1" dbsourceConnect="connectionstring2" provider="oracle">
<sqlQueries>
<add name="querynumber3"
sqlFilePath="C:\Users\me\Desktop\oracle"/>
<add name="querynumber4"
sqlFilePath="C:\Users\me\Desktop\oracle"/>
</sqlQueries>
</databaseConnection>
</connectionconfigurations>
The issue is that I am having trouble accessing all my parameters. How do I create nested config sections like this and access them through code?
I've made a class like this to handle the connection section:
public class Connectionconfigurations : ConfigurationSection
{
private static ConfigurationPropertyCollection _connectionConfiguration;
private static ConfigurationPropertyCollection _sqlQueries;
private static ConfigurationPropertyCollection _properties;
static Connectionconfigurations()
{
_connectionConfiguration = new ConfigurationProperty(
ConfigConstants.CONFIG_DATABASECONNECTION,
typeof(DatabaseConnectionElementCollection),
null,
ConfigurationPropertyOptions.IsRequired);
_sqlQueries = new ConfigurationProperty(
ConfigConstants.CONFIG_SQLQUERIES,
typeof(DatabaseConnectionElementCollection),
null,
ConfigurationPropertyOptions.IsRequired);
_properties = new ConfigurationPropertyCollection();
// Add other properties
_properties.Add(_databaseConnection);
}
[ConfigurationProperty("databaseConnection")]
public DatabaseConnectionElementCollection DatabaseConnection
{
get { return (DatabaseConnectionElementCollection)base[_databaseConnection]; }
}
}
However, I am getting the error: "Unrecognized attribute 'dbname'. Note that attribute names are case-sensitive."
DatabaseConnectionElement class:
public class DatabaseConnectionElement : ConfigurationElement
{
private static ConfigurationProperty _name;
private static ConfigurationProperty _sourceConnect;
private static ConfigurationProperty _provider;
private static SqlQueryElementCollection _sqlCollection; // Collection of sql queries for this DB element
private static ConfigurationPropertyCollection _properties;
static DatabaseConnectionElement()
{
_name = new ConfigurationProperty(ConfigConstants.CONFIG_DB_NAME, typeof(string), string.Empty,
ConfigurationPropertyOptions.IsKey);
_sourceConnect = new ConfigurationProperty(ConfigConstants.CONFIG_DB_SOURCECONNECT, typeof(string), string.Empty,
ConfigurationPropertyOptions.IsRequired);
_provider = new ConfigurationProperty(ConfigConstants.CONFIG_DB_PROVIDER, typeof(string), string.Empty,
ConfigurationPropertyOptions.IsRequired);
_sqlCollection = new SqlQueryElementCollection();
_properties = new ConfigurationPropertyCollection();
_properties.Add(_name);
_properties.Add(_sourceConnect);
_properties.Add(_reconnectDelay);
}
[ConfigurationProperty("dbname", IsKey = true)]
public string Name
{
get
{
return (string)base[_name];
}
}
[ConfigurationProperty("dbsourceConnect", IsRequired = true)]
public string SourceConnect
{
get
{
return (string)base[_sourceConnect];
}
}
[ConfigurationProperty("provider", IsRequired = true)]
public string Provider
{
get
{
return (string)base[_provider];
}
}
protected override ConfigurationPropertyCollection Properties
{
get
{
return _properties;
}
}
}
I think this might be helpful. I created a generic config section that has a list of subelements. I just called them elements. You could call them whatever you want.
/// <summary>
/// Defines a generic custom configuration section with a collection of elements of type T.
/// Reference: http://www.abhisheksur.com/2011/09/writing-custom-configurationsection-to.html
/// </summary>
public class GenericSection<T> : ConfigurationSection
where T : ConfigurationElement, IConfigurationElement, new()
{
// Attribute argument must be a constant expression.
protected const string _elementsTag = "elements";
public GenericSection()
{
}
[ConfigurationProperty(_elementsTag, Options = ConfigurationPropertyOptions.IsDefaultCollection)]
public GenericElementCollection<T> Elements
{
get { return ((GenericElementCollection<T>)(base[_elementsTag])); }
set { base[_elementsTag] = value; }
}
}
Here's the Generic Element collection.
/// <summary>
/// Defines a generic ConfigurationElementCollection
/// </summary>
[ConfigurationCollection(typeof(IConfigurationElement))]
public class GenericElementCollection<T> : ConfigurationElementCollection
where T : ConfigurationElement, IConfigurationElement, new()
{
internal const string _elementName = "elements";
protected override string ElementName
{
get { return _elementName; }
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
}
protected override bool IsElementName(string elementName)
{
return elementName.Equals(_elementName, StringComparison.InvariantCultureIgnoreCase);
}
public override bool IsReadOnly()
{
return false;
}
protected override ConfigurationElement CreateNewElement()
{
return new T();
}
/// <summary>
/// Return key value for element.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
/// <remarks></remarks>
protected override object GetElementKey(ConfigurationElement element)
{
return ((T)element).Name;
}
/// <summary>
/// Default index property.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index] // #c160704 was IConfigruationElement
{
get { return (T)BaseGet(index); }
}
/// <summary>
/// Returns content element by name.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public T GetElementByName(string name)
{
return (T)BaseGet(name);
}
public IEnumerable<T> Elements
{
get
{
for (int index = 0; index < this.Count; index++) yield return (T)BaseGet(index);
}
}
/// <summary>
/// Add an element to the collection
/// </summary>
/// <param name="element"></param>
public void AddElement(T element)
{
BaseAdd(element);
}
}
This is the interface I use for the Elements. It requires that the element have a Name property and an active flag.
public interface IConfigurationElement
{
string Name { get; set; }
bool Active { get; set; }
}
Here's a an example of instantiating and using the generic to create configuration section that has a list of folders.
Instantiation the GenericSection with the Element Type.
/// <summary>
/// Defines a custom configuration section for folder elements.
/// Reference: http://www.abhisheksur.com/2011/09/writing-custom-configurationsection-to.html
/// </summary>
public class FolderSection : GenericSection<FolderElement>
{
// This section doesn't require any addition properties.
}
We don't need to do anything with the GenericElementCollection.
Here's the FolderElement:
/// <summary>
/// Defines a configuration folder
/// </summary>
public class FolderElement : ConfigurationElement, IConfigurationElement
{
protected const string NameKey = "name";
protected const string VolumeKey = "volume";
protected const string PathKey = "path";
[ConfigurationProperty(NameKey, DefaultValue = "", IsKey = true, IsRequired = true)]
public string Name
{
get { return (string)base[NameKey]; }
set { base[NameKey] = value; }
}
[ConfigurationProperty(VolumeKey, DefaultValue = "", IsKey = false, IsRequired = false)]
public string VolumeLabel
{
get { return (string)base[VolumeKey]; }
set { base[VolumeKey] = value; }
}
[ConfigurationProperty(PathKey, DefaultValue = "", IsKey = false, IsRequired = true)]
public string Path
{
get { return (string)base[PathKey]; }
set { base[PathKey] = value; }
}
[ConfigurationProperty("active", DefaultValue = "true", IsKey = false, IsRequired = false)]
public bool Active
{
get { return (bool)base["active"]; }
set { base["active"] = value; }
}
}
Here's what my App.config looks like. I have two folder sections.
<configuration>
<configSections>
<section name="recent-folders" type="Common.Config.FolderSection, Common.Core"/>
<section name="hidden-folders" type="Common.Config.FolderSection, Common.Core"/>
</configSections>
...
<hidden-folders>
<elements>
<add name="folder1" volume="OS" path="C:\some\hidden\path" />
<add name="folder2" volume="OS" path="C:\some\other\hidden\path" />
</elements>
</hidden-folders>
<recent-folders>
<elements>
<add name="folder1" volume="OS" path="C:\Some\path" />
<add name="folder2" volume="OS" path="C:\Some\other\path" />
</elements>
</recent-folders>
</configururation>
I need to serialize a list of strings as CDATA and thought I would follow an answer of How do you serialize a string as CDATA using XmlSerializer.
It works like a charm for serializing. My XML file looks as desired:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<tlist>
<item><![CDATA[First string]]></item>
<item><![CDATA[Second string]]></item>
</tlist>
</root>
But deserialization does not work. The TestList remains empty; value in the setter has count 0. What have I missed?
[XmlRootAttribute("root")]
public class TestConfig
{
public TestConfig()
{
TestList = new List<string>();
CdataList = new List<XmlCDataSection>();
}
[XmlIgnore]
public List<string> TestList { get; set; }
[XmlArray("tlist")]
[XmlArrayItem("item")]
public List<XmlCDataSection> CdataList
{
get { return TestList.Select(a => new XmlDocument().CreateCDataSection(a)).ToList(); }
set
{
TestList = value.Select(s => s.Value).ToList();
}
}
public void Save(string path)
{
var serializer = new XmlSerializer(GetType());
using (var stream = new StreamWriter(path))
{
serializer.Serialize(stream, this);
}
}
public static TestConfig Load(string path)
{
var serializer = new XmlSerializer(typeof(TestConfig));
using (var stream = new StreamReader(path))
{
return (TestConfig)serializer.Deserialize(stream);
}
}
}
Executing:
var t = new TestConfig();
t.TestList.Add("First string");
t.TestList.Add("Second string");
t.Save(#"C:\Test\cdatatest.xml");
var r = TestConfig.Load(#"C:\Test\cdatatest.xml");
Console.WriteLine("Testlist size is {0}", r.TestList.Count);
I thought I would "improve" on the answer by softwariness by reducing the length of the XMLWriter subclass.
/// <summary>
/// Custom XmlWriter.
/// Wraps up another XmlWriter to intercept string writes within
/// elements and writes them as CDATA instead.
/// </summary>
public class XmlCDataWriter : XmlTextWriter
{
public override void WriteString(string text)
{
if (WriteState == WriteState.Element)
{
WriteCData(text);
}
else
{
base.WriteString(text);
}
}
/// <summary>
/// Creates an instance of the XmlTextWriter class using the specified <see cref="T:System.IO.TextWriter"/>.
/// </summary>
/// <param name="w">The TextWriter to write to. It is assumed that the TextWriter is already set to the correct encoding. </param>
public XmlCDataWriter( [NotNull] TextWriter w ) : base( w )
{
}
}
It can then be used with a StringBuffer like so:
using (StringWriter textWriter = new StringWriter())
{
XmlSerializer serializer = new XmlSerializer( typeof( ... ) );
serializer.Serialize(new XmlCDataWriter(textWriter), ... );
return textWriter.ToString();
}
Seems to work so far for me, and it is a MUCH smaller subclass :)
Whilst simple proxies work with single values, you have to do a deeper proxying for collections, because of the way the .NET XML serialization machinery works:
[XmlRootAttribute("root")]
public class TestConfig
{
public TestConfig()
{
TestList = new List<string>();
}
private List<string> testList;
[XmlIgnore]
public List<string> TestList
{
get
{
if (this.testList == null)
{
var newCollection = new List<string>();
if (this.cdataList != null)
{
foreach (var x in this.cdataList)
{
newCollection.Add(x.Value);
}
}
this.testList = newCollection;
this.cdataList = null;
}
return this.testList;
}
set
{
this.testList = value;
this.cdataList = null;
}
}
private List<XmlCDataSection> cdataList;
[XmlArray("tlist")]
[XmlArrayItem("item")]
public List<XmlCDataSection> CdataList
{
get
{
if (this.cdataList == null)
{
var newCollection = new List<XmlCDataSection>();
if (this.testList != null)
{
foreach (var x in this.testList)
{
newCollection.Add(new XmlDocument().CreateCDataSection(x));
}
}
this.cdataList = newCollection;
this.testList = null;
}
return this.cdataList;
}
set
{
this.cdataList = value;
this.testList = null;
}
}
public void Save(string path)
{
var serializer = new XmlSerializer(GetType());
using (var stream = new StreamWriter(path))
{
serializer.Serialize(stream, this);
}
}
public static TestConfig Load(string path)
{
var serializer = new XmlSerializer(typeof(TestConfig));
using (var stream = new StreamReader(path))
{
return (TestConfig)serializer.Deserialize(stream);
}
}
}
The problem is that the serialization code doesn't just get and set the collections in one go. For example, when it's deserializing, it either creates a new collection, or gets one that's already set on the property, and adds to it. If you've created a new collection here computed from the "real" collection that your application needs to deal with, then any changes to the computed collection won't be reflected in the "real" collection.
To work around this, what I've done in the code above is to transfer ownership of the collection from the "real" collection to the "proxy" collection, and back again, depending on which collection property is being accessed. The cost of transferring ownership is incurred only when switching from one property to the other, so successive accesses to the "real" TestList collection in your application won't incur that expense.
This is somewhat inelegant though if you have many such collections. If you wanted to have all your element text serialized as CDATA, you could implement a custom XmlWriter, like the following:
/// <summary>
/// Custom XmlWriter.
/// Wraps up another XmlWriter to intercept string writes within
/// elements and writes them as CDATA instead.
/// </summary>
public class XmlCDataWriter : XmlWriter
{
XmlWriter w;
public XmlCDataWriter(XmlWriter baseWriter)
{
this.w = baseWriter;
}
public override void Close()
{
w.Close();
}
public override void Flush()
{
w.Flush();
}
public override string LookupPrefix(string ns)
{
return w.LookupPrefix(ns);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
w.WriteBase64(buffer, index, count);
}
public override void WriteCData(string text)
{
w.WriteCData(text);
}
public override void WriteCharEntity(char ch)
{
w.WriteCharEntity(ch);
}
public override void WriteChars(char[] buffer, int index, int count)
{
w.WriteChars(buffer, index, count);
}
public override void WriteComment(string text)
{
w.WriteComment(text);
}
public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
w.WriteDocType(name, pubid, sysid, subset);
}
public override void WriteEndAttribute()
{
w.WriteEndAttribute();
}
public override void WriteEndDocument()
{
w.WriteEndDocument();
}
public override void WriteEndElement()
{
w.WriteEndElement();
}
public override void WriteEntityRef(string name)
{
w.WriteEntityRef(name);
}
public override void WriteFullEndElement()
{
w.WriteFullEndElement();
}
public override void WriteProcessingInstruction(string name, string text)
{
w.WriteProcessingInstruction(name, text);
}
public override void WriteRaw(string data)
{
w.WriteRaw(data);
}
public override void WriteRaw(char[] buffer, int index, int count)
{
w.WriteRaw(buffer, index, count);
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
w.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteStartDocument(bool standalone)
{
w.WriteStartDocument(standalone);
}
public override void WriteStartDocument()
{
w.WriteStartDocument();
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
w.WriteStartElement(prefix, localName, ns);
}
public override WriteState WriteState
{
get { return w.WriteState; }
}
public override void WriteString(string text)
{
if (WriteState == WriteState.Element)
{
w.WriteCData(text);
}
else
{
w.WriteString(text);
}
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
w.WriteSurrogateCharEntity(lowChar, highChar);
}
public override void WriteWhitespace(string ws)
{
w.WriteWhitespace(ws);
}
}
You'd then use it like follows:
var serializer = new XmlSerializer(...));
using (var cdataWriter = new XmlCDataWriter(XmlWriter.Create("somepath.xml")))
{
serializer.Serialize(cdataWriter, myDocumentObject);
}
Again, this only makes sense as an option if you want to write everything as CDATA.
I am using Unity and have a Model tagged with data annotations:
public class SomeModel
{
[SlackDisplayName("ED0CAD76-263E-496F-ABB1-A4DFE6DEC5C2")]
public String SomeProperty { get; set; }
}
This SlackDisplayName property is a child class of DisplayName, which resolves a static Display Name for the property. I just wanted to make that dynamically by having this criteria fulfilled:
The use of this annotation is possible.
I can implement multilingual applications using that annotation.
Language templates get identified by a GUID
I must not pass a culture id to the annotation
So furthermore, my SlackDisplayName annotation looks like this:
/// <summary>
/// Annotation for non-fixed display names
/// </summary>
public class SlackDisplayNameAttribute : DisplayNameAttribute
{
/// <summary>
/// TODO
/// </summary>
/// <param name="identifierGUID"></param>
public SlackDisplayNameAttribute(String identifierGUID)
: this(Guid.Parse(identifierGUID))
{
}
/// <summary>
/// TODO
/// </summary>
/// <param name="identifier"></param>
public SlackDisplayNameAttribute(Guid identifier)
: base()
{
}
/// <summary>
/// The culture context to use.
/// </summary>
[Dependency]
public ICultureContext Context { get; set; }
/// <summary>
/// Gets the display name for the given GUID.
/// </summary>
public override string DisplayName
{
get
{
return "NOT_DEFINED";
//return Context.GetLanguageTemplate(new Guid());
}
}
}
And now the question is: How to get the ICultureContext from my Unity Container:
[Dependency]
public ICultureContext Context { get; set; }
It is registered, but I have no clue about how to get that property injected.
I have solved it myself!
First of all, you need the following Unity Extension and Strategy:
Info: Found here: UnityContainer.BuildUp() - Can I make it inject new instances into properties only if these are null?
public class RecursiveBuildUpContainerExtension : UnityContainerExtension {
protected override void Initialize(){
Context.Strategies.Add( new RecursiveBuildUpBuilderStrategy( Context.Container ), UnityBuildStage.PreCreation );
}
}
public class RecursiveBuildUpBuilderStrategy : BuilderStrategy {
readonly IUnityContainer container;
public RecursiveBuildUpBuilderStrategy( IUnityContainer container ) {
this.container = container;
}
public override void PreBuildUp( IBuilderContext context ) {
if( context.Existing == null ) return;
foreach( var prop in context.Existing.GetType( ).GetProperties( ) ) {
if( ContainsType<DependencyAttribute>( prop.GetCustomAttributes( true ) ) ) {
if( prop.GetValue( context.Existing, null ) == null ) {
var value = container.Resolve( prop.PropertyType );
prop.GetSetMethod( ).Invoke( context.Existing, new[] { value } );
}
else {
var value = container.BuildUp( prop.PropertyType, prop.GetValue( context.Existing, null ) );
prop.GetSetMethod( ).Invoke( context.Existing, new[] { value } );
}
}
}
foreach (var method in context.Existing.GetType().GetMethods() ){
if( ContainsType<InjectionMethodAttribute>( method.GetCustomAttributes( true ))){
var argsInfo = method.GetParameters( );
var args = new object[argsInfo.Length];
for( int i = 0; i < argsInfo.Length; i++ ) {
args[i] = container.Resolve( argsInfo[i].ParameterType );
}
method.Invoke( context.Existing, args );
}
}
context.BuildComplete = true;
}
private static bool ContainsType<T>( IEnumerable<object> objects ){
foreach (var o in objects){
if( o is T ) return true;
}
return false;
}
}
You need this, because it is responsible for injecting the properties on "BuildUp". Next to this, you need to register your extension
container.AddNewExtension<RecursiveBuildUpContainerExtension>();
Furthermore, you need to override the default DataAnnotationsModelMetadataProvider, because the default ModelMetaDataProvider does not use Unity to inject properties to annotations. To do this, implement this class:
public class DynamicModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
private IUnityContainer _context;
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
foreach (Attribute attribute in attributes)
_context.BuildUp(attribute);
return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
}
public DynamicModelMetadataProvider(IUnityContainer context)
: base()
{
this._context = context;
}
}
After that, edit your bootstrapper and set the new ModelMetadataProvider, to make it clear to the MVC Framework that it has to use it:
ModelMetadataProviders.Current = new DynamicModelMetadataProvider(container);
Where container is your set up IUnityContainer. You should now have instances in your Annotations Instance when having set the [DependencyAttribute] and your Methods marked with the [InjectionMethod] should get called.
[Dependency]
public ICultureContext Context { get; set; }
Hope you could use this if you had a similar problem ;)