Mapping an MVC ViewModel to a List of Name/Value Pairs

Sponsored by #native_company#
#native_desc# #native_cta#

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!

Matthew Jones

Matthew Jones

I'm a parent, a husband, a geek, a web developer, and a speaker, in roughly that order.

Read More
Mapping an MVC ViewModel to a List of Name/Value Pairs
Share this