Model-to-Entity Mapper in ASP.NET

We all know we're not supposed to link our MVC2 models directly to entities in our Entity Framework. Doing so would let form hackers modify all sorts of data that you'd probably prefer to keep unmodified. Unfortunately, I haven't found good methods of copying model values to entities and vice-versa. I didn't feel like writing a million lines of code to copy each value explicitly, so I rolled a little tool that will convert a model to an entity and vice-versa using the power of reflection in C#.

Basically, you can create an entity via the Entity Framework (these are usually generated automatically by the Visual Studio IDE, though you can add functionality by writing partial classes) and a corresponding model via MVC2 and have complete control over which properties are able to be set on each form by specifying an array of property names. It's pretty simple, as you can see by the few number of lines I used to implement it.

It's simplicity is a warning, though, because this tool probably won't work in a variety of scenarios. For one, it will only copy the public properties of your model/entity; fields are ignored. For two, only shallow copies will be made of any reference types that exist in your objects. For three, nullable entity values aren't handled very well, as I can't seem to get at the attributes which define nullable values in the Entity Framework via reflection.

Anyway, enjoy. The source is below, and a short example follows.

    /// <summary>
    /// This is a class with a couple of static methods that make it easy to copy
    /// an MVC2 model's property values to an Entity-Framework entity.
    /// </summary>
    /// <typeparam name="T">The type of the model declared in your MVC2 project.</typeparam>
    /// <typeparam name="t">The type of the entity declared in your Entity Framework project.</typeparam>
    public class EntityModeler<T, t>
        where T : EntityModel<t>
        where t : System.Data.Objects.DataClasses.EntityObject
    {
        /// <summary>
        /// A new model of type T is created whose properties will have the same
        /// value as the entity parameter respective of the property name.
        /// </summary>
        /// <param name="entity">
        /// The entity whose property values should be copied to the model.
        /// </param>
        /// <returns>The new model object of type T.</returns>
        public static T ModelEntity(t entity)
        {
            // create an object of type model and populate its properties
            // with the value of the entity's properties. Then return the
            // model.
            var model = System.Activator.CreateInstance<T>();
            model.UpdateModel(entity);
            return model;
        }
 
        /// <summary>
        /// A new IEnumerable set of models is created whose properties have the
        /// same value as the entities' properties in the parameter.
        /// </summary>
        /// <param name="entities">
        /// The entities whose properties should be copied to the models.
        /// </param>
        /// <returns>An IEnumerable set of models of type T.</returns>
        public static IEnumerable<T> ModelEntities(IEnumerable<t> entities)
        {
            // Loop through all the entities in the entity list.
            // Create a model type for each entity. This model will have its
            // UpdateModel method called to copy the entity property values to
            // the model. Then just add the model to a list and return the
            // list.
            var list = new List<T>();
            foreach (var entity in entities)
                list.Add(EntityModeler<T, t>.ModelEntity(entity));
            return list;
        }
    }
 
    /// <summary>
    /// This is the base class for all MVC2 models that can be converted to an entity defined
    /// by a class in an entity framework. Each child class should define its entity's type
    /// using the "T" type parameter. This class provides methods to update its model properties
    /// based on an entity and vice versa.
    /// </summary>
    /// <typeparam name="T">
    /// The type of the entity that corresponds to the model that inherits this base class. This
    /// object must descend from the base EntityObject type.
    /// </typeparam>
    public abstract class EntityModel<T> 
        where T : System.Data.Objects.DataClasses.EntityObject
    {
        /// <summary>
        /// This method updates the inheriting model's properties to correspond with the 
        /// properties of an entity. The entity must be of the type defined by the inheriting
        /// model. Reflection is used to get the public, readable properties from the entity
        /// and the public, writable properties from the model. Then, we just write the entity's
        /// value to the model for each property that shares the same name.
        /// </summary>
        /// <remarks>
        /// Please note that this method will only copy an entity's public properties. Fields
        /// are ignored (though they could easily be added). Also, this method will only perform
        /// a shallow copy of the entity's properties.
        /// </remarks>
        /// <param name="entity">
        /// The entity object whose properties should be copied to this model.
        /// </param>
        public void UpdateModel(T entity)
        {
            // get all the public properties of this model in an array
            var myProperties = this.GetType().GetProperties(
                BindingFlags.Instance |
                BindingFlags.Public
                );
            // now get the type of the entity
            var entityType = entity.GetType();
            // loop through the properties
            foreach (var myProp in myProperties)
            {
                // try to get the property with the same name from the entity.
                // If we can read from the entity property and write to this
                // object's property, then set this object's property according
                // to the value of the entity property. But first, check if the
                // value is null. If the value is null, then the corresponding
                // property in this object must be of type Nullable.
                var entityProp = entityType.GetProperty(myProp.Name);
                if (entityProp != null && entityProp.CanRead && myProp.CanWrite)
                {
                    var val = entityProp.GetValue(entity, null);
                    if (val == null)
                        if (myProp.PropertyType != typeof(Nullable))
                            continue;
                    myProp.SetValue(this, entityProp.GetValue(entity, null), null);
                }
            }
        }
 
        /// <summary>
        /// This method updates the properties of an entity based on the values of
        /// this model's properties. Reflection is used to get the properties of the
        /// entity and the properties of this model, then the values of the model's
        /// properties are copied to those of the entity that share the same name.
        /// </summary>
        /// <remarks>
        /// Please note that this method only works on public properties. Fields are
        /// ignored (though they could easily be added). Also, this method will only
        /// perform a shallow copy of the model's properties.
        /// </remarks>
        /// <param name="entity">
        /// This is the entity object whose properties should be updated. It must be
        /// of the type defined by the inheriting class.
        /// </param>
        /// <param name="includeProperties">
        /// This is an array of string values that represent the names of the properties
        /// that the method should copy. If a property's name does not exist in this
        /// array, then its value will not be copied.
        /// </param>
        public void UpdateEntity(ref T entity, params string[] includeProperties)
        {
            // get all the public properties of this model in an array
            var propertyInfo = this.GetType().GetProperties(
                BindingFlags.Instance |
                BindingFlags.Public
                );
            // now get the type of the entity.
            var entityType = entity.GetType();
            // loop through each of the properties in the property array for the model type
            foreach (var myProp in propertyInfo)
            {
                // check if this property is in the list of properties to include
                if (includeProperties.Contains(myProp.Name))
                {
                    // now try to get the property of the entity with the same name as the
                    // property of this type.
                    var entityProp = entityType.GetProperty(myProp.Name);
                    // check that the entity property exists and that we can write to it.
                    if (entityProp != null && entityProp.CanWrite && myProp.CanRead)
                    {
                        // get the current value of this property in the model object.
                        // If this property can't be read, the value will be set to null.
                        var val = myProp.GetValue(this, null);
                        // if the value is null, make sure the corresponding property of the
                        // entity is nullable. If it isn't, do not try to set the value. Just
                        // go to the next value in the array.
                        // TODO: We need a better method to determine if an entity property
                        // is nullable. This method won't allow NULL to be written to nullable
                        // columns.
                        if (val == null)
                            if (entityProp.PropertyType != typeof(Nullable))
                                continue;
                        // we're here, so the value should be ok to set. Set the value of the
                        // entity object to the value of the model object.
                        entityProp.SetValue(entity, val, null);
                    }
                }
            }
        }
 
        /// <summary>
        /// This method works similarly to the one above, only it creates a new entity
        /// instead of updating an existing one. The entity will be of the type T which
        /// was declared by the inheriting class.
        /// </summary>
        /// <param name="includeProperties">
        /// This is an array of string values that represent the names of the properties
        /// that the method should copy. If a property's name does not exist in this
        /// array, then its value will not be copied. 
        /// </param>
        /// <returns>
        /// The return value is the new entity with its properties set equal to
        /// the respective properties of this model (well, at least those that were
        /// specified in the parameter).
        /// </returns>
        public T CreateEntity(params string[] includeProperties)
        {
            var entity = System.Activator.CreateInstance<T>();
            this.UpdateEntity(ref entity, includeProperties);
            return entity as T;
        }
    }
        // Create your model with any properties you choose. You won't
        // have to worry about people hacking your forms, because you'll
        // define the properties that are allowed to be inserted/updated
        // as you convert the model to an entity.
        public class PersonModel : EntityModel<PersonEntity>
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Address { get; set; }
            public string SocialSecurity { get; set; }
        }
 
        // Create your MVC2 controller with an Index and Edit page.
        public class PersonController : Controller
        {
            public ActionResult Index()
            {
                // Create a list of models based on every Person entity in your
                // data set.
                var models =
                    EntityModeler<PersonModel, PersonEntity>.ModelEntities(
                        YourEntities.PersonSet
                        );
                return View("Index", models);
            }
 
            [HttpPost]
            public ActionResult Edit(PersonModel model)
            {
                if (!ModelState.IsValid)
                    return View(model);
                // Create an Person entity based on the Person model submitted
                // by a user, allowing only the FirstName, LastName, and Address
                // properties to be copied. This will protect the properties in
                // your entity that shouldn't be editable by users (for example,
                // a SocialSecurity property).
                var entity = model.CreateEntity(
                    "FirstName",
                    "LastName",
                    "Address"
                    );
 
                // The entity is ready to be used by your Entity Framework project,
                // so you can add your UPDATE logic and error checking here.
                // YourEntities.AddToPersonSet(entity);
                // ...
                // ...
 
                return RedirectToAction("Details", new { id = entity.Id });
            }
        }

2 comments for 'Model-to-Entity Mapper in ASP.NET'

1. Working example

Hey,

This looks exactly like something I could use... Would you have a working example?

2. Example

The second example is pretty close to a working example. You would just need to copy the PersonModel to your Models directory and the PersonController to your Controllers directory. Though, you would need some sort of entity framework for your PersonEntity object...

I suppose that example isn't complete enough. I'll work on something and post it in a bit.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Image links with 'rel="lightbox"' in the <a> tag will appear in a Lightbox when clicked on.

More information about formatting options

CAPTCHA
Please verify your species.