Exploring all things .NET and beyond...

Using ValueInjecter With ViewModels and Entity Framework in ASP.NET MVC

If you decide to use ASP.NET MVC with the Entity Framework, you will quickly come to see that the perfect one entity per view scenario that most of the examples show is not realistic. Enter the need for ViewModels. The ViewModels are specially shaped classes that act as an intermediary between the 1...n entities that will eventually comprise a single view (or partial view).

The challenge is when it comes to repopulating the original 1...n entities that comprised the ViewModel upon the user making an insert or an update. In our perfect one entity to one view scenario, MVC model binding takes care of all the work repackaging our entity in the controller, and it is a matter of a few lines of code to add that object to the EF context and save the changes. Code in this scenario would be as straight forward as the following:

[HttpPost]
public ActionResult Create(Contact contact)
{
    if (ModelState.IsValid)
    {
      using (var context = new AdventureWorksEntities())
      {
        //Add the model bound object to the context and save:
        context.Contacts.AddObject(contact);
        context.SaveChanges();
        return RedirectToAction("Index");
      }
    }
    return View(name);
}
When using ViewModels, a convenient option to help reduce any manual mapping back to entities is to use an open source product named ValueInjecter. You can download the binaries from NuGet located here.

Value Injecter allows us to easily map properties from a source object to a destination object without what would be normally required to rehydrate the multiple entities. The main method we will use is the exposed InjectFrom() which will allow us to inject the values from the source to destination object. Once complete, we can then persist our entities back to the data store using EF with only a minimal amount of additional coding.

To begin let's take a look at the following ViewModel class I created based on (3) different tables from the AdventureWorks database: Contact, Employee, and EmployeeDepartmentHistory. The idea here is that we need to add a new employee record here that spans these (3) tables. The focus here is not on the schema from the AdventureWorks database but rather just to display how Value Injecter can be used to persist data to these (3) entities. As you can see below the ViewModel class has fields from each of the tables; however for brevities sake I have kept the class small. In reality there are quite a few more required fields that would have to be defined.

namespace Mcv4ValueInjecterSample.ViewModels
{
  public class EmployeeViewModel
  {
    [StringLength(50), Required]
    [Display(Name = "Employee First Name:")]
    public string FirstName { get; set; }

    [StringLength(50), Required]
    [Display(Name = "Employee Last Name:")]
    public string LastName { get; set; }

    [StringLength(50), Required]
    [Display(Name = "Email Address:")]
    [DataType(DataType.EmailAddress)]
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage="Please enter a properly formatted email address.")]
    public string EmailAddress { get; set; }

    [StringLength(25), Required]
    [Display(Name = "Phone Number:")]
    [DataType(DataType.PhoneNumber)]
    public string Phone { get; set; }

    [Required]
    [Display(Name = "Birth Date:")]        
    public DateTime BirthDate { get; set; }

    [StringLength(50), Required]
    [Display(Name = "Title:")]
    public string Title { get; set; }

    [Required]
    [Display(Name = "Employee Start Date:")]
    public DateTime StartDate { get; set; }
  }
}
Next I have an EmployeeController class with a Create() method that takes in my ViewModel type as its parameter. I am going to inject the values from the model bound ViewModel object data into the individual underlying entities and then save the changes.
[HttpPost]
public ActionResult Create(EmployeeViewModel employeeViewModel)
{
  if (ModelState.IsValid)
  {
    using (var context = new AdventureWorksEntities())
    {
      var contact = new Contact();  
      //Inject the values into a Contact entity using ValueInjecter                  
      contact.InjectFrom(employeeViewModel);

      //Do NOT use the title submitted by the client, 
      //but rather override with a default value
      contact.Title = string.Empty;

      var employee = new Employee();  
      //Inject the values into an Employee entity using ValueInjecter                  
      employee.InjectFrom(employeeViewModel);
        
      var employeeDepartmentHistory = new EmployeeDepartmentHistory();
      //Inject the values into a EmployeeDepartmentHistory entity using ValueInjecter
      employeeDepartmentHistory.InjectFrom(employeeViewModel);
        
      employee.EmployeeDepartmentHistories.Add(employeeDepartmentHistory);
      contact.Employees.Add(employee);

      context.Contacts.AddObject(contact);
      context.SaveChanges();

      return RedirectToAction("Index");
    }
    return View(employeeViewModel);
  }
}
Note the explicit assignment of the Title property on the Contact object above. In the Adventureworks database, both the Contact and Employee tables have a Title field. My Employee view had an input for the client intended to fill out the Employee's title and not the Contact's title. Value Injecter by default will not discriminate and simply hydrate the destination object based on the matched property name (as designed so this is OK). Just make sure to intervene where needed if you run into a situation with identically named properties on objects.

That's it! There is actually a lot more functionality to ValueInjecter than what was shown here so I encourage you to look at the Documentation section on its CodePlex page. However even just this example can be scaled nicely to large legacy or existing databases where the 1:1 view per entity scenario rarely occurs. In this case consider using ValueInjecter to save on a lot of additional coding when using ViewModels in ASP.NET MVC.

0 comments:

Post a Comment