An interesting scenario came up at work the other day. I have a class (corresponding to a database table) which can store any piece of data in a name-value pair. This is so we can store any number of pieces of "incidental" information in our database that is shared among many web apps.

That class looked like this:

  public class DataPair
  {
      public string Name { get; set; }
      public string Value { get; set; }
  }

Now what I need is for a user to come along and use a ViewModel to fill out a bunch of information that will be turned into a List and submitted to a database for storage.

I was sick and tired of seeing code like this:

    public ActionResult Add(AddDescriptionVM model)
    {
      //Don't do this
      List<DataPair> data = DataPairManager.GetForItem(id);
      AddDescriptionVM model = new AddDescriptionVM();
      model.Description = data.Where(x=>x.Name == "Description").FirstOrDefault();
      model.StartDate = DateTime.Parse(data.Where(x=>x.Name == "StartDate").FirstOrDefault());
      //save to database
      return RedirectToAction("Index");
    }

That's just riddled with potential errors. What I'd rather do is use an attribute on the ViewModel so that I can associate DataPair names with ViewModel properties.

Here's what I came up with:

    public class FieldNameAttribute : Attribute
    {
        private string _FieldName;
        public string FieldName
        {
            get
            {
                return _FieldName;
            }
            set
            {
                _FieldName = value;
            }
        }
        public FieldNameAttribute(string fieldName)
        {
            FieldName = fieldName;
        }
    }

Now, in my viewmodel, I can identify which piece of data goes with which property, like so:

    public class AddDescriptionVM
    {
        [FieldName("Description")]
        public string Description { get; set; }

        [FieldName("StartDate")]
        public DateTime StartDate { get; set; }
    }

Which is part of the solution. However, I still need a way to take a given ViewModel and actually map the values to a List<DataPair>. And I'd like to do it generically, since then I can have all ViewModels inherit from one base ViewModel and use this method over.

Here's the base ViewModel:

    public class BaseViewModel
    {
        public List<DataPair> MapToDataPair()
        {
            List<DataPair> data = new List<DataPair>();
            var properties = this.GetType().GetProperties();
            foreach (var property in properties)
            { 
                var attribute = property.GetCustomAttributes(typeof(FieldNameAttribute), false).FirstOrDefault();
                if (attribute != null)
                {
                    data.Merge(((FieldNameAttribute)attribute).FieldName, ParseValue(property.PropertyType, property.GetValue(this)));
                }
            }

            return data;
        }
   private string ParseValue(Type type, object value)
      {
          if(value == null || value == DBNull.Value)
          {
             return string.Empty;
          }
          if(type.IsEnum)
          {
              return Enum.GetName(type, value); //assumes we want to store the enum name, not value
          }
          if(type == typeof(DateTime))
          {
              return DateTime.Parse(value.ToString()).ToShortDateString();
          }
          else return value.ToString();
        }
    }

I also needed a way to deal with Enums, which is included in that ParseValue() method.

Now, since the ViewModel can return a new list of items, I can have AddDescriptionVM inherit from BaseViewModel:

    public class AddDescriptionVM : BaseViewModel

And in my POST action, I can get the list of data back:

    public ActionResult Add(AddDescriptionVM model)
    {
        List<DataPair> dataPairs = model.MapToDataPair();
        //save to database
        return RedirectToAction("Index");
    }

So now I no longer have all that mapping code getting in my way.

You can grab the source from Dropbox if you'd like to see it in action.

Happy Coding!