I want to create a custom attribute that when decorated on a property, it "sets" the property to the value of the attribute. In this case, I am reading an excel file and want to map its value to a property.
using System;
namespace Excel.DataBind
{
[AttributeUsage(AttributeTargets.Property)]
public class ExcelDataBindAttribute : Attribute
{
private ExcelReader _excelReader;
public ExcelDataBindAttribute(string rangeAddress)
{
_excelReader = new ExcelReader();
_excelReader.GetExcelValue(rangeAddress);
// some code here to map it to the property it decorates...?
}
}
}
namespace Excel.Models
{
public class Model
{
[ExcelDataBind("A2")]
public string Value { get; set; }
}
}
I'm searching online to find a way to achieve this, but reflection is said as a good approach. But as i'm new to this, i'm not sure if it would be the best approach. Can someone here direct me?
Thank you.
First of all, the attribute should (as the name suggests) only decorate a model a such. An separate binder class should than do the magic. Something like this:
using Excel.DataBind;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Excel.DataBind
{
public class ExcelDataBinder
{
public void DataBind(ExcelDocument doc, object target)
{
var lookup = new Dictionary<string, PropertyInfo>();
// loop through all properties of the target.
foreach(var prop in target.GetType().GetProperties())
{
// if the property has an decorator, store this.
var address = prop.GetCustomAttribute<ExcelDataBindAttribute>()?.Address;
if(!string.IsNullOrEmpty(address))
{
lookup[address] = prop;
}
}
// loop through all excel fields
foreach(var field in doc)
{
// if a mapping is defined
if(lookup.TryGetValue(field.Address, out var prop))
{
// use reflection to set the value.
prop.SetValue(target, field.Value);
}
}
}
}
[AttributeUsage(AttributeTargets.Property)]
public class ExcelDataBindAttribute : Attribute
{
public ExcelDataBindAttribute(string address) => Address = address;
public string Address { get; }
}
}
namespace Excel.Models
{
public class Model
{
[ExcelDataBind("A2")]
public string Value { get; set; }
}
}
This approach can also be used to to write to Excel based on a model of course.
Note that setting the value can be tricky. Your ExcelDocument representation might use different types than your model (decimal vs double etc.) In that case you have to convert that too.
Another remark: In my experience (I've written code like that in the past) in real world scenario's the model represent just a row of an excel sheet tab. Than you need something with an header row, and should be defensive on column orders. (You still need attributes to describe the relation between the Excel truth and you code truth however).
Related
I am working on an application that stores data in the ConfigurationManager.AppSettings file, and I am wanting to implement it in a different way than how I do right now. Currently, I have an interface (see below) that each class with saveable traits needs to implement, then call the static save methods from my Config class (example below). I don't like the coupling between my Config class and the class with the saveable data, so my ideal would be to have an attribute that indicates a property should be saved. Then, instead of calling the SaveData or LoadData functions in my manager class, I would call a function that sets/saves all the attributed properties. This seems similar to how [Serializeable] works in default C#, so I imagine it's possible somehow. However, most of my searches have been fruitless. Any ideas on how to implement something like this?
Interface
Example
Reflection is what you're looking for.
Reflection provides objects (of type Type) that describe assemblies, modules, and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. If you are using attributes in your code, reflection enables you to access them.
Assuming that you're only interested in properties, you can use typeof or GetType to get an instance of System.Type. You can then call GetProperties to get an IEnumerable<PropertyInfo>. PropertyInfo has an Attributes property that you can use to retrieve the attributes for that property. You can also use an instance of PropertyInfo to retrieve the value of the property.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public class MyAttribute : Attribute
{
}
public class Foo
{
[My]
public string Bar { get; set; }
public string Baz { get; set; }
[My]
public string Id { get; set; }
}
public static class Utilities
{
public static IEnumerable<PropertyInfo> GetPropertiesWithMyAttribute(object obj)
{
return obj.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(pi => pi.CustomAttributes.Any(ca => ca.AttributeType == typeof(MyAttribute)));
}
}
class Program
{
static void Main(string[] args)
{
var foo = new Foo()
{
Bar = "Bar_Value",
Baz = "Baz_Value",
Id = "Id_Value"
};
foreach (var pi in Utilities.GetPropertiesWithMyAttribute(foo))
{
Console.WriteLine($"{pi.Name}: {pi.GetMethod.Invoke(foo, null).ToString()}");
}
foreach (var pi in Utilities.GetPropertiesWithMyAttribute(foo))
{
pi.SetMethod.Invoke(foo, new object[] { $"{pi.Name}_Value_Reflection" });
}
Console.WriteLine(foo.Bar);
Console.WriteLine(foo.Baz);
Console.WriteLine(foo.Id);
Console.ReadKey();
}
}
Of course, this example only string properties. You're going to have to figure out some way to deal with properties that aren't strings; for example you haven an ObservableCollection in your example.
I want to create a method that displays the information contained in an object, that will work dynamically, with any object. I'm having trouble handling properties that are other custom classes. In the example below the Person has Phones and Occupations which both are other classes. When the data is displayed, the value on the screen currently is:
TestReflection.Person
Name: Mary
Phones: TestReflection.Phones
Occupations: TestReflection.Occupations
It just displays the name of class, like TestReflection.Phones, rather than the data inside that object.
How can I change this code to show information like this instead?
TestReflection.Person
Name: Mary
Phones:
TestReflection.Phones
Type: 1
Number: 555XYZ
Occupations:
TestReflection.Occupations
Type: 5
Description: Secretary
Here is my code:
class Program
{
static void Main(string[] args)
{
List<Person> listPeson = new List<Person>();
var person1 = new Person();
person1.Name = "Mary";
person1.Phones = new Phones { new Phone { Type = 1, Number = "555XYZ" } };
person1.Occupations = new Occupations {new Occupation { Type = 5, Description = "Secretary" }};
listPeson.Add(person1);
DynamicExport(listPeson);
Console.ReadLine();
}
public static void DynamicExport<T>(List<T> listReg)
{
for (int i = 0; i < listReg.Count; i++)
{
Console.WriteLine(listReg[i].GetType());
foreach (var item in listReg[i].GetType().GetProperties())
{
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
}
}
}
}
class Person
{
public string Name { get; set; }
public Phones Phones { get; set; }
public Occupations Occupations { get; set; }
}
class Phones : List<Phone> { }
class Phone
{
public int Type { get; set; }
public string Number { get; set; }
}
class Occupations : List<Occupation> { }
class Occupation
{
public int Type { get; set; }
public string Description { get; set; }
}
I made some edits to your question - I hope I understood you correctly.
If you want to export data
If your question is really about displaying data, then there are better ways to do it than creating your own export method. The format you are trying to display looks similar to YAML. There's also JSON and XML. Using one of these libraries is probably better than writing your own method:
YamlDotNet NuGet package
Json.NET NuGet Package
System.Xml.Serialization.XmlSerializer class
If you want to learn more about reflection
Maybe you're interested in learning more about reflection, and the export is just an example to play around with it. In that case, let's look at this line:
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
$"{item.GetValue(listReg[i], null)}" ends up calling person1.Phones.ToString(). The default behavior of ToString just displays the type name. You could override that behavior, like this:
class Phones : List<Phone>
{
public override string ToString()
{
return Program.DynamicExportToString(this);
// ... where DynamicExportToString is a modified version of DynamicExport that
// builds and returns a string rather than sending it directly to the Console.
}
}
Maybe you want to be able to handle any class, even when you cannot override ToString in all of the classes you might export. Then you will need to put some additional logic in the DynamicExport method, because...
$"{item.Name}: {item.GetValue(listReg[i], null)}"
... doesn't work for every situation. We need to display different things depending on the type of the property.
Consider how you want to handle null values. Maybe something like $"{item.Name}: <null>"
Use your existing $"..." code if the type is...
a primitive type.
DateTime
String
... or a Nullable<> of one of those types.
If the type implements IEnumerable, loop over the contents of the collection and recursively call your export code for each element.
It's important to check for this interface after you've checked if the type is a String, because String implements IEnumerable.
Otherwise, recursively call your export code on this value.
When you call your export code recursively, it would be wise to guard against infinite loops. If the object you're trying to export contains a circular reference - you could quickly wind up with a StackOverflowException. To avoid this, maintain a stack of objects that have already been visited.
I think the above advice is generally applicable whenever you're using reflection to traverse an object graph - whether it's for serialization or any other purpose.
I hope this helps!
I have classes that might or might not change their name (and members) during development. My classes are used (in most cases) like enums, but I couldn't use enums because I needed slightly more functionality. Since classes (obviously) don't have an Integer representing them under the surface I need to create some solution for having similar functionality. In other words, I want for each class to be represented by an Integer (or some other unique identifier).
I've created this attribute:
public class IdAttribute : Attribute
{
private int id = -1;
public IdAttribute(int index)
{
this.id = index;
}
public int Id
{
get
{
return id;
}
}
}
And I'm using it as following:
[Id(0)]
public class Hello: Core { }
[Id(1)]
public class Bye: Core { }
As you can see it's quite error prone, since I don't want any class to have the same Id. And thus, optimally I want an automatic generated id, but I don't want it to change if I ever change anything regarding the class, for example the class name or its members.
What's the best way to achieve this?
(I know that in Java, that once you make a class Serializable, you'll get an automatically generated id (is there something like this in C#?).)
EDIT:
The reason I "couldn't" just use enums is because of (mainly) convenience. I have classes which exposes fields in an editor. And in this editor I can select only the appropriate "enums", in some cases only enums which inherits from "Core" will be displayed and in other cases they might inherit from "Tools" or some other class. I hope that cleared up a bit.
Not sure why you'd need to do this, but you could do the following:
[AttributeUsage(AttributeTargets.Class)]
public class IdAttribute:Attribute
{
public Guid Id { get; }
public IdAttribute(string id)
{
Id = new Guid(id);
}
}
And you'd use it like:
[IdAttribute("7d7952d1-86df-4e2e-b040-fed335aad775")]
public class SomeClass
{
//example, you'd obviously cache this
public Guid Id => GetType().GetCustomAttribute<IdAttribute>().Id;
//...
}
Do note, that Guids are not random. If you need a random id, then this isn't the solution. To generate a Guid read comments to your question.
You can handle that through your base class Core:
public abstract class Core
{
public Core()
{
Type myType = this.GetType();
object[] attrs = myType.GetCustomAttributes(typeof(IdAttribute), false);
IdAttribute attr = attrs?.OfType<IdAttribute>().FirstOrDefault();
int id = -1;
if (attr != null) id = attr.Id;
if (!reservedIdentities.ContainsKey(id))
{
reservedIdentities.Add(id, myType);
}
else
{
if (!reservedIdentities[id].Equals(myType))
throw new ArgumentException("Duplicate identities discovered.", nameof(id));
}
}
static Dictionary<int, Type> reservedIdentities = new Dictionary<int, Type>();
//...
}
I want to create a key value table in my database along the lines of
public class KeyValue {
public string Id { get; set; }
public dynamic Value {get; set; }
}
Using a slightly modified SqlProvider I have no problems getting CreateTable<KeyValue>() to generate varchar(1024) Id, varchar(max) Value.
I have no issues saving objects to it. The problem is when I load the objects
var content = dbConn.GetById<KeyValue>("about");
content.Value at this point is a string.
Looking at the database record, the text for value does not appear to store any type information.
Is there really anything I can do better other than manually invoking ServiceStack.Text and call deserialize with the appropriate type information?
I do not need absolute dynamic, my actual use case is for polymorphism with a base class instead of dynamic. So I don't really care what type Value is whether it's the base class, dynamic, object, etc. Regardless other than using the class
public class KeyValue {
public string Id { get; set; }
public MySpecificChildType Value {get; set; }
}
I haven't been able to get anything other than a string back for Value. Can I tell OrmLite to serialize the type information to be able to correctly deserialize my objects or do I just have to do it manually?
Edit: some further information. OrmLite is using the Jsv serializer defined by ServiceStack.Text.TypeSerializer and is in no way pluggable in the BSD version. If I add a Type property to my KeyValue class with the dynamic Value I can do
var value = content.Value as string;
MySpecificChildType strongType =
TypeSerializer.DeserializeFromString(content, content.Type);
I just really want a better way to do this, I really don't like an object of 1 type going into the db coming back out with a different type (string).
I haven't worked much with the JsvSerializer but with the JsonSerializer you can achieve this (in a few different ways) and as of ServiceStack 4.0.11 you can opt to use the JsonSerializer instead, see https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#v4011-release-notes.
Example
public abstract class BaseClass {
//Used for second example of custom type lookup
public abstract string Type { get; set; }
}
public class ChildA : BaseClass {
//Used for second example of custom type lookup
public override string Type { get; set; }
public string PropA { get; set; }
}
And then in your init/bootstrap class you can configure the serializer to emit the type information needed for proper deserialization:
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
}
}
If you wish to use something other that the default "__type" attribute that ServiceStack uses (if you for example want to have a friendly name identifying the type rather then namespace/assembly) you can also configure your own custom type lookup as such
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
ServiceStack.Text.JsConfig.TypeAttr = "type";
ServiceStack.Text.JsConfig.TypeFinder = type =>
{
if ("CustomTypeName".Equals(type, StringComparison.OrdinalIgnoreCase))
{
return typeof(ChildA);
}
return typeof(BaseClass);
}
}
}
I've refactored code like this:
public string CamelCASE { get; set; }
to:
public string CamelCase {get; set; }
only do discover that the input XML contains the former casing (let's call it a shouting camel). I have no control over how the XML document is produced. Nor do I burn of desire to retract my changes.
I'd like to map the loud camel property to a softly speaking one.
I've tried XmlElement and XmlMapping but to no greater success. A googling gave me only hits on how to map stuff to attributes, along lines of this post. However, I need only something like <LoudCAMEL> to be deserialized to a property public string QuietCamel.
Is there a smooth way to do so?
Edit
After adding the attribute as follows:
using System.Collections.Generic;
using System.Xml;
public class Beep : SuperBeep
{
private readonly BeepType _a;
public Beep() { _a = BeepType.SomeSome; }
public Beep(BeepType input) { _a = input; }
~Beep() { }
public override void Dispose() { }
public BeepType Aaa { get { return _a; } }
[XmlElement("CamelCASE")]
public bool CamelCase { get; set; }
}
I can see the red, wavy highlight telling me Cannot access constructor 'XmlElement' here due its protection level. When I compile, though, I get the IDE crying out loud that 'System.Xml.XmlElement' is not an attribute class.
Frankly, I'm a bit confused by the suggestion to use attributes (this is targeting .NET 2.0), since I was under the impression that attributing wasn't available to .NET prior to version 3.5. Am I mistaken?
[XmlElement("CamelCASE")]
public string CamelCase { get; set; }
should be all you need, if you are keeping the shouty name in the xml. If you want to use the quieter name in new xml, but allow the old name to still work, it gets more complicated. You could use:
public string CamelCase { get; set; }
[XmlElement("CamelCASE"), Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public string CamelCaseLegacy {
get { return CamelCase; }
set { CamelCase = value; }
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeCamelCaseLegacy() { return false; }
When serializing, the CamelCase property will serialize to <CamelCase>, and the CamelCaseLegacy element will be ignored due to the ShouldSerialize* method. However, when deserializing, the CamelCaseLegacy property will be used whenever <CamelCASE> is seen. We then map this value back to the CamelCase property.
You are referring to the wrong namespace.
Remove
using System.Xml;
and add
using System.Xml.Serialization;