An interesting scenario came up at work the other day. I have a class (which corresponds to a SQL 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 view model to fill out a bunch of information that will be turned into a List<T> 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 view model so that I can associate DataPair names with view model 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 view model, 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 view model and actually map the values to an instance of List<DataPair>. And I'd like to do it generically, since then I can have all view models inherit from one common base class and use this method over.

Speaking of, here's the base view model class, aptly named BaseViewModel:

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)
        {
            //Assumes we want to store the enum name, not value
            return Enum.GetName(type, value); 
        }
        if(type == typeof(DateTime))
        {
            return DateTime.Parse(value.ToString()).ToShortDateString();
        }
        else return value.ToString();
    }
}

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

Now, since the view model 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.

Happy Coding!