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!