XML Element Name from T in Generic Class - c#

For a current project I am trying to make have a generic XML creator using the XmlSerializer class - I need a certain element in one of the classes to have the ElementName set based on the type of class that the creator is in in this context that is simple enough. Here is an example:
public abstract class ElementTypeBase
{
public abstract string ElementName { get; }
}
public class ElementTypeA : ElementTypeBase
{
public override string ElementName
{
get { return "ElementA"; }
}
}
Then pass this to my XML object class which will be used for the XmlSerializer but I want the ElementName to be specific to the type.
public class XMLObject<T> where T : ElementTypeBase
{
[XmlElement(Activator.CreateInstance<T>().ElementName)]
public string SomeElement;
}
I thought I would be able to do that but get:
An attribute argument must be a constant expression, typeof expression
or array creation expression of an attribute parameter type
So I thought that I could override ToString() but this doesn't work, I was thinking of using constants but it feels dirty. Any other suggestions?

You can do that through the XmlAttributeOverrides class like shown below.
However, you'll need to cache the XmlSerializer instance and I wouldn't recommend this approach for your problem.
when you say that you have one element that is gonna be different for each inherited object, I'd suggest to put this element in the inherited class, not the generic one, and hard-code its XmlElement.ElementName.
using System;
using System.Xml.Serialization;
public class Program
{
static void Main(string[] args)
{
XmlSerializer serializer =
new XmlSerializer(typeof(XMLObject<MyElement>),
XMLObject<MyElement>.Overrides);
serializer.Serialize(Console.Out,
new XMLObject<MyElement>() { SomeElement = "value" });
Console.ReadLine();
}
}
public class XMLObject<T> where T : ElementTypeBase, new()
{
public static XmlAttributeOverrides Overrides { get; private set; }
static XMLObject()
{
Overrides = new XmlAttributeOverrides();
Overrides.Add(typeof(XMLObject<T>), "SomeElement",
new XmlAttributes
{
XmlElements =
{
new XmlElementAttribute(new T().ElementName)
}
});
}
public string SomeElement { get; set; }
}
public abstract class ElementTypeBase
{
public abstract string ElementName { get; }
}
public class MyElement : ElementTypeBase
{
public override string ElementName
{
get { return "ElementA"; }
}
}

Related

Can I call a static method that lives on a base class with a reference the the class implementing that base class (in C#)?

I'm having a hard time phrasing the question which is also making it hard for me to search for answers.
Here's a contrived scenario that mimics what I'd like to do:
void Main()
{
Console.WriteLine(TestClassA.MyPropertyName());
Console.WriteLine(TestClassB.MyPropertyName());
var speaker = new TestSpeaker();
speaker.Speak<TestClassA>();
speaker.Speak<TestClassB>();
}
public class TestSpeaker {
public void Speak<T>() where T : BaseClass<T> {
Console.WriteLine(/* I want to call T.MyPropertyName() here */);
}
}
public class TestClassA : BaseClass<TestClassA> {
public string Name { get; set; }
}
public class TestClassB : BaseClass<TestClassB> {
public string OtherPropertyName { get; set; }
}
public abstract class BaseClass<T> {
public static string MyPropertyName(){
return typeof(T).GetProperties().Single().Name;
}
}
The Console right now would read:
Name
OtherPropertyName
I'd like to replace my commented out code so that it would read:
Name
OtherPropertyName
Name
OtherPropertyName
if you change your Writeline to
Console.WriteLine(BaseClass<T>.MyPropertyName());
you will get what you want
Why use a static function in a base class to retrieve information about a derived class? In any case, you could implement a member function to wrap the static call:
public static string MyStaticFunction() => return "whatever";
public string MyMemberFunction() => MyStaticFunction();
But in your scenario, perhaps you should simply declare an abstract property (or function) meant to return the value you're looking for and override it in derived classes:
Base:
public abstract string MyPropertyName { get; }
Derived:
public override string MyPropertyName => nameof(OtherPropertyName); // or more complex logic
And yet another possible solution would be to pass the information to the base class's constructor as a string (or property expression should you be so inclined):
Base:
public string MyPropertyName { get; init; }
public BaseClass(string propertyName)
{
MyPropertyName = propertyName; // maybe validate that the property exists
}
Derived:
public MyTestClassB() : BaseClass(nameof(OtherPropertyName)) {}

associate a string with a class so that I can retrieve the string if class is given as generic type parameter

For different classes I need to attach a string to the class (i.e. Class1 has the string hello, Class2 has the string world etc.). Then I will have a generic type parameter T somewhere that will be (at runtime) one of these classes. I need to be able to retrieve the associated string from that generic type parameter.
How do I set this up and make it work?
Since all classes are written by me, I can use every possible approach (e.g. define common interface for them or common base class or whatever).
I tried creating a base class that has a public static field containing the string, and for each actual class "overwrite" (hide base and create new) the string. But it turned that I still could not retrieve the string when only having the type parameter T.
public class BaseClass
{
public static string Get => "";
}
public class Class1 : BaseClass
{
public static new string Get => "hello";
}
public class Class2 : BaseClass
{
public static new string Get => "world";
}
public class Testing<T> where T : BaseClass
{
public void Test()
{
string s = T.Get;
// compiler error: "'T' is a type parameter, which is not valid in the given context"
// strangely though, BaseClass.Get and Class1.Get and Class2.Get work fine!
}
}
Real-world use case:
I have a static class MySerializer<T> that is supposed to deserialize objects of type T. During deserialization I want to validate if my object of type T conforms to the schema associated with type T.
In order to validate I need to add a schema first. For each class T that can be deserialized there is a different schema which I store in my project as an embedded resource, so each schema has a path (like a file path). That means: for each class T I need to associate a string (the path) with the class so that I am able to get that path out of T.
Here is the relevant part of my serializer and the schema adding process:
public static class MySerializer<T>
{
private static readonly XmlSerializer _mySerializer = new XmlSerializer(typeof(T));
private static readonly XmlReaderSettings _settings = new Func<XmlReaderSettings>(() =>
{
System.Reflection.Assembly assy = typeof(MySerializer<T>).Assembly;
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(null,
XmlReader.Create(assy.GetManifestResourceStream(T.GetAssociatedString())));
// T.GetAssociatedString(): How to make this work?
return new XmlReaderSettings
{
Schemas = schemas,
ValidationType = ValidationType.Schema,
ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings |
XmlSchemaValidationFlags.ProcessIdentityConstraints
};
})();
public static T Deserialize(Stream strm)
{
using (XmlReader reader = XmlReader.Create(strm, _settings))
{
return (T)_mySerializer.Deserialize(reader);
}
}
}
Since static methods and generic type parameters don't work together in C# (thanks to Matthew Watson for linking to Eric Lippet's Blog) and I don't want to create a new instance of T solely to be able to call a method, I'll go with attributes.
[AttributeUsage(AttributeTargets.Class)]
class SomeStringAttribute : Attribute
{
public string SomeString { get; set; }
public SomeStringAttribute(string s)
{
SomeString = s;
}
}
[SomeString("hello")]
public class Class1
{
}
[SomeString("world")]
public class Class2
{
}
public class Testing<T>
{
public void Test()
{
string s =
((SomeStringAttribute)typeof(T).GetCustomAttributes(typeof(SomeStringAttribute),
false)[0]).SomeString;
}
}
You could use reflection to do this, but note that all the properties need to be static (your sample code above introduces non-static properties to the derived class).
Here's a compilable example:
using System;
namespace Demo
{
public class BaseClass
{
public static string Get => "";
}
public class Class1 : BaseClass
{
public new static string Get => "hello";
}
public class Class2 : BaseClass
{
public new static string Get => "world";
}
public class Testing<T> where T : BaseClass
{
public string Test()
{
var property = typeof(T).GetProperty("Get");
if (property != null)
return (string) property.GetValue(null, null);
return null;
}
}
class Program
{
static void Main()
{
var test1 = new Testing<Class1>();
Console.WriteLine(test1.Test()); // Prints "hello"
var test2 = new Testing<Class2>();
Console.WriteLine(test2.Test()); // Prints "world"
}
}
}
In this code, the where T : BaseClass isn't actually needed for it to compile and work, but you might want to keep it to make it clear that it is only supposed to be used with classes that inherit from BaseClass.

How to use XmlSerializer to serialize derived instances?

I realize this looks to be an exact duplicate of Using XmlSerializer to serialize derived classes, but I cannot figure out how to get this working following the guidance from that same question:
using System;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace xmlSerializerLab
{
public class Utf8StringWriter : System.IO.StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
[XmlRoot(ElementName = "Query", Namespace = "http://www.opengis.net/wfs")]
public class Query
{
[XmlElement(ElementName = "Filter", Namespace = "http://www.opengis.net/ogc")]
public Filter Filter { get; set; }
}
[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlInclude(typeof(LiteralFilter))]
[XmlInclude(typeof(Query))]
[Serializable]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}
public class PropertyIsOpFilter : Filter, IXmlSerializable
{
public Filter LeftOp { get; set; }
public Filter RightOp { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader) { }
public void WriteXml(XmlWriter writer)
{
Program.ToXml(LeftOp, writer);
Program.ToXml(RightOp, writer);
}
}
[XmlRoot("IsEqualTo")]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }
public class OpFilterBase : Filter, IXmlSerializable
{
public string Op { get; set; }
public object Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader) { }
public void WriteXml(XmlWriter writer)
{
if (!String.IsNullOrEmpty(Op))
{
writer.WriteStartElement(Op);
writer.WriteValue(Value);
writer.WriteEndElement();
}
else
{
writer.WriteValue(Value);
}
}
}
public class LiteralFilter : OpFilterBase { }
class Program
{
public static void ToXml(Object o, XmlWriter writer)
{
var inputSerializer = new XmlSerializer(o.GetType(), new Type[] {
typeof(Filter),
typeof(PropertyIsOpFilter),
typeof(PropertyIsEqualToFilter),
typeof(OpFilterBase),
typeof(LiteralFilter),
typeof(Query)
});
inputSerializer.Serialize(writer, o);
}
public static string ToXml(Object o)
{
var inputSerializer = new XmlSerializer(o.GetType());
using (var writer = new Utf8StringWriter())
{
using (var xmlWriter = new XmlTextWriter(writer))
{
ToXml(o, xmlWriter);
}
return writer.ToString();
}
}
static void Main(string[] args)
{
Filter o = new PropertyIsEqualToFilter()
{
LeftOp = new LiteralFilter()
{
Value = 1
},
RightOp = new LiteralFilter()
{
Value = 1
}
};
var query = new Query()
{
Filter = o
};
Console.WriteLine(ToXml(query));
Console.ReadLine();
}
}
}
It results in this exception:
InvalidOperationException: The type
xmlSerializerLab.PropertyIsEqualToFilter may not be used in this
context. To use xmlSerializerLab.PropertyIsEqualToFilter as a
parameter, return type, or member of a class or struct, the parameter,
return type, or member must be declared as type
xmlSerializerLab.PropertyIsEqualToFilter (it cannot be object).
Objects of type xmlSerializerLab.PropertyIsEqualToFilter may not be
used in un-typed collections, such as ArrayLists.
As far as I can tell, I need the IXmlSerializable on the PropertyIsOpFilter and OpFilterBase because I'm trying to target a specific XML format described by this schema. But I'm finding that I also have to make the Query class IXmlSerializable.
Here is a sample XML document that I'd like to be able to produce from the model:
<GetFeature xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
service="WFS"
version="1.1.0"
maxFeatures="0" xmlns="http://www.opengis.net/wfs">
<ResultType>Results</ResultType>
<OutputFormat>text/gml; subtype=gml/3.1.1</OutputFormat>
<Query
d2p1:srsName="EPSG:4326" xmlns:d2p1="http://www.opengis.net/ogc">
<d2p1:Filter>
<d2p1:IsEqualTo>
<d2p1:PropertyName>Prop1</d2p1:PropertyName>
<d2p1:Literal>1</d2p1:Literal>
</d2p1:IsEqualTo>
</d2p1:Filter>
</Query>
</GetFeature>
By making the Query class IXmlSerializable and writing a good bit of WriteXml and ReadXml logic I can get it to work, but I'd expect it to work without having to do all that since the XmlRoot and XmlAttribute and XmlElement tags should give enough information to the serializer for it to know which class to instantiate based on the tag name (match ElementName) and certainly how to serialize based on the attributes.
The problem you are seeing can be reproduced with the following minimal example:
public class BaseClass
{
}
public class DerivedClass : BaseClass, IXmlSerializable
{
#region IXmlSerializable Members
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader) { throw new NotImplementedException(); }
public void WriteXml(XmlWriter writer) { }
#endregion
}
Using the serialization code:
BaseClass baseClass = new DerivedClass();
using (var textWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(textWriter))
{
var serializer = new XmlSerializer(typeof(BaseClass), new Type[] { typeof(DerivedClass) });
serializer.Serialize(xmlWriter, baseClass);
}
Console.WriteLine(textWriter.ToString());
}
The following exception is thrown (sample fiddle #1):
System.InvalidOperationException: There was an error generating the XML document.
---> System.InvalidOperationException: The type DerivedClass may not be used in this context. To use DerivedClass as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type DerivedClass (it cannot be object). Objects of type DerivedClass may not be used in un-typed collections, such as ArrayLists.
This is among the most unhelpful exception messages I have seen from XmlSerializer. To understand the exception, you need to understand how XmlSerializer handles polymorphism via the [XmlInclude] mechanism. If I remove IXmlSerializable from DerivedClass, the following XML is generated (fiddle #2):
<BaseClass xsi:type="DerivedClass" />
Notice the xsi:type type attribute? That is a w3c standard attribute that XmlSerializer uses to explicitly assert the type of a polymorphic element; it is documented here. When XmlSerializer is deserializing a polymorphic type to which [XmlInclude] attributes have been applied (either statically or through the constructor you are using), it will look for the xsi:type attribute to determine the actual type to construct and deserialize.
It is this, apparently, which conflicts with IXmlSerializable. A type which implements this interface should completely control its XML reading and writing. However, by parsing and interpreting the xsi:type attribute, XmlSerializer has already begun automatic deserialization, and so throws an exception due to the inconsistent deserialization strategies of the base and derived types.
What's more, adding IXmlSerializable to the base type doesn't really fix the problem either If you do so, the xsi:type attribute is never written, and later, when ReadXml() is called, an object of the base type will get unconditionally constructed, as shown in fiddle #3.
(It's conceivable that Microsoft could have implemented a special case where XmlSerializer begins automatic deserialization, then "backs off" and hands the task over to ReadXml() when an IXmlSerializable polymorphic type is encountered and constructed. But, they did not.)
The solution would seem to be to serialize your Filter types automatically using the [XmlInclude] mechanism. In fact I don't see any reason you need to use IXmlSerializable, and was able to serialize your model successfully by removing IXmlSerializable completely and making some minor changes to namespaces:
public static class XmlNamespaces
{
public const string OpengisWfs = "http://www.opengis.net/wfs";
}
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Query
{
public Filter Filter { get; set; }
}
[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}
[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsOpFilter : Filter
{
public Filter LeftOp { get; set; }
public Filter RightOp { get; set; }
}
[XmlRoot("IsEqualTo", Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }
[XmlInclude(typeof(LiteralFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class OpFilterBase : Filter
{
public string Op { get; set; }
public object Value { get; set; }
}
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class LiteralFilter : OpFilterBase { }
Notes:
For the [XmlInclude] mechanism to work, all the included types apparently must be in the same XML namespace as the base type. To ensure this I added [XmlRoot(Namespace = XmlNamespaces.OpengisWfs)] to all the Filter subtypes.
The [XmlInclude(typeof(DerivedType))] attributes can be added either to their immediate parent type or to the lowest common base type. In the code above I added the attributes to the immediate parent types so that members of an intermediate type could be serialized successfully, e.g.:
public class SomeClass
{
PropertyIsOpFilter IsOpFilter { get; set; }
}
Consider marking intermediate types that cannot be instantiated as abstract, e.g. public abstract class Filter. Consider marking types that are "most derived" as sealed, e.g. public sealed class LiteralFilter
If you use the new XmlSerializer(Type, Type []) constructor, you must statically cache the serializer to avoid a severe memory leak, as explained here. It's not necessary in my solution but you are using it in your question.
Sample fiddle #4 showing that the following XML is generated successfully:
<Query xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wfs">
<Filter xsi:type="PropertyIsEqualToFilter">
<LeftOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</LeftOp>
<RightOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</RightOp>
</Filter>
</Query>

How to deserialize xml to object with inheritance?

I have the following xml which represents 2 types of plugins, FilePlugin and RegsitryPlugin:
<Client>
<Plugin Type="FilePlugin">
<Message>i am a file plugin</Message>
<Path>c:\</Path>
</Plugin>
<Plugin Type="RegsitryPlugin">
<Message>i am a registry plugin</Message>
<Key>HKLM\Software\Microsoft</Key>
<Name>Version</Name>
<Value>3.5</Value>
</Plugin>
</Client>
I would like to deserialize the xml into objects.
As you can see, the 'Message' element is repeating itself both for the FilePlugin and the RegistryPlugin and i am using inheritance for this:
abstract class Plugin
{
private string _message;
protected Plugin(MISSING conf)
{
// here i need to set my private members like:
// Message = MISSING.Message;
}
}
class FilePlugin : Plugin
{
private string _path;
public FilePlugin(MISSING config)
: base(config)
{
// Here i need to set my private members like:
// _path = config.Path;
}
}
class RegistryPlugin : Plugin
{
private string _key;
private string _name;
private string _value;
public RegistryPlugin(MISSING config)
: base(config)
{
// Here i need to set my private members like:
// _key = config.Key;
// _key = config.Name;
// _key = config.Value;
}
}
}
I need somehow to deserialize the xml, and than to decide according the PluginType Element which Instance to create:
i.e:
if it is written in the xml the Type=FilePlugin than i need to create
Plugin p1 = new FilePlugin(conf);
if it is written in the xml the Type=RegistryPlugin than i need to create
Plugin p2 = new RegistryPlugin(conf);
Please follow my comments in my code in order to understand the missing parts.
Thanks
Creating your own deserializer isn't also hard. Here is my solution to this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml.Linq;
using System.Reflection;
using System.Text;
namespace WindowsFormsApplication1
{
public abstract class Plugin
{
public string Type { get; set; }
public string Message { get; set; }
}
public class FilePlugin : Plugin
{
public string Path { get; set; }
}
public class RegsitryPlugin : Plugin
{
public string Key { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
static class MyProgram
{
[STAThread]
static void Main(string[] args)
{
string xmlstr =#"
<Client>
<Plugin Type=""FilePlugin"">
<Message>i am a file plugin</Message>
<Path>c:\</Path>
</Plugin>
<Plugin Type=""RegsitryPlugin"">
<Message>i am a registry plugin</Message>
<Key>HKLM\Software\Microsoft</Key>
<Name>Version</Name>
<Value>3.5</Value>
</Plugin>
</Client>
";
Assembly asm = Assembly.GetExecutingAssembly();
XDocument xDoc = XDocument.Load(new StringReader(xmlstr));
Plugin[] plugins = xDoc.Descendants("Plugin")
.Select(plugin =>
{
string typeName = plugin.Attribute("Type").Value;
var type = asm.GetTypes().Where(t => t.Name == typeName).First();
Plugin p = Activator.CreateInstance(type) as Plugin;
p.Type = typeName;
foreach (var prop in plugin.Descendants())
{
type.GetProperty(prop.Name.LocalName).SetValue(p, prop.Value, null);
}
return p;
}).ToArray();
//
//"plugins" ready to use
//
}
}
}
My idea:
Add methods ToXml and FromXml to Plugin, and lets this method implementation in Plugin load/save only common properties like Message in your example;
Override these methods in derived classes, and call base methods in overridden ones to load common parameters, as well as add custom code to load specific parameters.
Create static method in LoadXml in Plugin that reads XML file, gets type name, creates this type instance dynamically with reflection and calls its FromXml method.
You may use what is called a "memento pattern".
The idea is that you have some extra object especially for serialization-deserialization.
So you may have something like:
public class PluginMemento {
[XmlAttribute] public string Type { get; set; }
[XmlElement] public string Name { get; set; }
[XmlElement] public string Message { get; set; }
[XmlElement] public string Key { get; set; }
..... //all the other properties
}
[XmlRootAttribute("Client")]
public class Client {
[XmlElementAttribute("Plugin")]
public PluginMemento[] plugins;
}
Now you should be able to deserialize your Xml into the Client type.
Then you can enumerate plugins, and start creating instances based on PluginMemento.Type property (via reflection, or by using a factory or a factory method) by passing the PluginMemento into the constructor of the class specified in the Type property.
The factory method can be very simple:
public static Plugin CreatePlugin(PluginMemento memento) {
switch(memento.Type) {
case "FirstPlugin": return FirstPlugin(memento);
case "SecondPlugin": return SecongPlugin(memento);
}
}
Reflection may be more smart and interesting, but requires a bit more coding.
I would implement the Factory Pattern with a Class which produces you the concrete Plugin.

Problem with XML serialisation and C#

I am trying to serialise some C# classes to XML. Things were going fine until I tried to introduce some inherited classes.
The classes are [edited for size]
public class ParticipationClass
{
[XmlAttribute]
public string typeCode;
[XmlAttribute]
public string contextControlCode;
public ParticipationClass()
{
}
}
public class p_recordTarget : ParticipationClass
{
public p_recordTarget()
{
typeCode = "RCT";
contextControlCode = "OP";
}
}
when using the classes the following code is used :
public class Test
{
// other class attributes excluded..
[XmlElement]
public ParticipationClass recordTarget;
private void Test()
{
recordTarget = new p_recordTarget();
}
}
The serialisation fails with an InvalidOperationException, looking in the exception detail I can see "Message="The type itk.cda.cdaclasses.p_recordTarget was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."
So I guess I need an XmlInclude, but I am not sure how.
In a nutshell, you need to use the attribute on the base class to let the serializer know about the derived classes:
[XmlInclude(typeof(p_recordTarget))]
public class ParticipationClass {
// ...
}
This may help you out:
http://www.codeproject.com/KB/XML/xmlserializerforunknown.aspx
Like this:
[XmlInclude(typeof(p_recordTarget))]
public class ParticipationClass
{
[XmlAttribute]
public string typeCode;
[XmlAttribute]
public string contextControlCode;
public ParticipationClass()
{
}
}

Categories

Resources