Saturday, May 12, 2012

Copy specific object properties using reflection and inferface

Building view models for use within your MVC solution is great to combine objects for a strongly typed view. And I have been all about them. I just encountered a situation where I needed to update values for specific models contained within the view model. Getting the view model back from the view was the easy part thanks to MVC.

        [Authorize]
        public ActionResult Index()
        {
            var context = new Context();
            var repo = new UserRepository(context);

            var user = repo.GetUserByEmail(User.Identity.Name);

            var account = new Account { User = user };

            return View(account);
        }

In a perfect world I would have loved to just have done this

        [Authorize]
        [HttpPost]
        public ActionResult Index(Account account)
        {
            UpdateModel(account.User);
            repo.Save();

            return RedirectToAction("Index");
        }

This immediately didn't told me it wouldn't work. Update what model? and in what context??? The solution I am making rebuilds the context each get/post. Is there a better way, probably, but this is what I do for now. After searching for some .net built in solutions, I started creating an update function that would copy values from the modified object into a context object, allow Entity Framework to detect the change and commit. First round looked like this.

        public void Update(User copyToUser, User copyFromUser)
        {
            var type = typeof(User);
            foreach (var pi in type.GetProperties())
            {
                var copyValue = copyFromUser.GetType().GetProperty(pi.Name).GetValue(copyFromUser, null);

                copyToUser.GetType().GetProperty(pi.Name).SetValue(copyToUser, copyValue,null);
            }

        }

This was working great, except it was modifying values related to identity and references! And then creating new objects! ACK! While I could have hard coded specific property names to either be skipped or evaluated, I wanted a more concrete and testable way.

I created a specific Interface under my current IUser and named it IUserDetailsCompare. Then instead of using the User type during my reflection loop I told the loop to evaluate the Interface. Done!

    public interface IUser
    {
        long Id { get; set; }
        string Email { get; set; }
        string NameFirst { get; set; }
        string NameLast { get; set; }
        DateTime DateJoined { get; set; }
        Guid Ref { get; set; }
        bool Active { get; set; }
        bool Disclaimer { get; set; }
    }

    public interface IUserDetailsCompare
    {
        string NameFirst { get; set; }
        string NameLast { get; set; }
    }

        public void Update(User copyToUser, User copyFromUser)
        {
            var type = typeof(IUserDetailsCompare);
            foreach (var pi in type.GetProperties())
            {
                var copyValue = copyFromUser.GetType().GetProperty(pi.Name).GetValue(copyFromUser, null);

                copyToUser.GetType().GetProperty(pi.Name).SetValue(copyToUser, copyValue,null);
            }

        }
I consider this a huge win in my strive to follow a more TDD approach.

1 comment:

  1. Good stuff, man.

    I ran into this same problem with MVC and EF, and ended up solving it nearly identically. One difference, though, was I had the Update() method examine the type of property on the model and had it ignore the EF specific reference stuff

    ReplyDelete