0

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.

Silent Kay
  • 161
  • 1
  • 9
  • [Have a look at this](https://stackoverflow.com/questions/2864343/how-to-get-and-modify-a-property-value-through-a-custom-attribute), it is encouraged to not change the value of a property from an attribute. But it is apparently possible. – Harjan Feb 13 '20 at 12:28
  • Attribute can not do it for you, it's the frameworks or your other parts of code which use attributes to do something. – Reza Aghaei Feb 13 '20 at 12:28
  • Thanks @Harjan, JSON.NET by Newtonsoft does something like this. You declare an attribute, which binds the value to the property. This is where the idea stemmed from. I would've thought, something as large as Newtonsoft wouldn't choose to do things that are "not-adhered". This approach makes for a clean implementation for mapping properties? – Silent Kay Feb 13 '20 at 12:32
  • @SilentKay Looking at the sourcecode for their attributes it is clear that they use a sepperate serializer for setting the values. I'm not sure how they have achieved this. – Harjan Feb 13 '20 at 12:44

1 Answers1

1

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).

Corniel Nobel
  • 421
  • 4
  • 12
  • Thank you for this. A question, where is the DataBinder called? – Silent Kay Feb 13 '20 at 15:54
  • That DataBinder should be called at the place you want to bind.So you get an ExcelDocument/stream, and when that happens, you use the DataBinder to initalize your model. – Corniel Nobel Feb 14 '20 at 10:13