Using Custom Attributes to make an Alias for an Object or Property

So you have a Domain Object with a property that takes its data from and external source, and users of the external source refer to it by a meaningless or changing name, what do you do?
Do you adopt the name used by the external system (potentially meaningless) or create a property name based on the original name and know that the code will become less meaningful over time?
Really what would be useful would be to name your property in a meaningful way and have a mechanism to alias the property so that it can be accessed by the external name too. Ideally, the external alias should be stored as closely as possible to the property to which it refers so that any changes to the alias have a very small footprint.

An Example: weather Statistics.

Imagine our system holds data about weather, and within it the data has relatively meaningful names SunnyDaysCount, RainyDaysCount, PlaceName etc, but the source from which the data came refers to these data items using arbitrary labels: “place_name”, “74569878” and “82339037” respectively. If we annotate the class with the external name we can use it to access the property via reflection.

using System;
namespace WeatherStat.Classes
{
    [AttributeUsage(AttributeTargets.Property)]
    public class ExportStat : Attribute
    {
        public readonly string StatName;

        public ExportStat(string statName)
        {
            StatName = statName;
        }
    }

    public class WeatherStatExport
    {
        public WeatherStatExport(string placeName, string sunnyDays, string rainyDays)
        {
            PlaceName = placeName;
            SunnyDaysCount = sunnyDays;
            RainyDaysCount = rainyDays;
        }

        [ExportStat("place_name")]
        public string PlaceName { get; set; }

        [ExportStat("74569878")]
        public string SunnyDaysCount { get; set; }

        [ExportStat("82339037")]
        public string RainyDaysCount { get; set; }
    }
}

So what we need is a Helper, that allows us to access the data by its alias:

using System;
using System.Collections.Generic;
using System.Linq;
using WeatherStat.Classes;

namespace WeatherStat.Helpers
{
    public static class AttributeAccessHelper
    {
        public static string GetValue(object model, string statName)
        {
            var prop = (from p in model.GetType().GetProperties()
                        let attr = p.GetCustomAttributes(typeof (ExportStat), true)
                        where attr.Length == 1
                        select
                        new {Property = p, Attribute = GetExportStat(attr, statName)})
                        .FirstOrDefault(x =>x.Attribute != null);

            if (prop != null)
            {
                var stat = prop.Property.GetValue(model, null) as string;
                return !string.IsNullOrEmpty(stat) ? stat : string.Empty;
            }
            return string.Empty;
        }

        private static ExportStat GetExportStat(IEnumerable</pre>
Here is test code to prove that the Helper works:


using NUnit.Framework;
using WeatherStat.Classes;
using WeatherStat.Helpers;

namespace WeatherStatTests
{
    [TestFixture]
    public class AttributeAccessHelperTests
    {
        [Test]
        public void DataAccessedViaPropertyOrAttributeYieldsSameValue()
        {
            var ws = new WeatherStatExport("London", "15", "16");
            var placeName = AttributeAccessHelper.GetValue(ws, "place_name");
            var sunnyDays = AttributeAccessHelper.GetValue(ws, "74569878");
            var rainyDays = AttributeAccessHelper.GetValue(ws, "82339037");
            Assert.AreEqual(ws.PlaceName, placeName);
            Assert.AreEqual(ws.SunnyDaysCount, sunnyDays);
            Assert.AreEqual(ws.RainyDaysCount, rainyDays);
        }
    }
}

So, we can keep the names of properties (or classes or whatever) meaningful inside our project, but still refer to them by aliases relating to external systems.