0

I want to Update a database table. The controller receives a view model with the appropriate data. If I assign every member variable like in the example below it works

[HttpPost]
public ActionResult Edit(Admin_vm vm) {
    var result = (from Users in db.Users
                              where Users.ID == usrID
                              select Users
                  ).FirstOrDefault();

    result.CompanyName = vm.ModifyUser.CompanyName;
    //this is where I would assign every single member variable
    db.SaveChanges();
    return View(vm);
}

However, I wonder if there is a way to directly assign the view model object like so:

result = vm.ModifyUser;

This does not give me an error, but it does not assign the member variables. Is there an easy way to do this?

Thanks!

mneumann
  • 713
  • 2
  • 9
  • 42
  • ViewModels are used to validate user inputs. They should processed, validated and then converted to entities. I would also used db.Users.FirstOrDefaultAsync(x=>x.Id == userID). If this return null, it means your user does not exit. In that case, your code will crash. –  Oct 18 '17 at 13:18
  • You mean I should not pass the view model to the controller? Sorry, I do not understand your point. – mneumann Oct 18 '17 at 13:23
  • You should pass the VM from the view to the controller. You should not pass it from the controller to data model :).In theory, your VM and your model may be different so it would not work. Hope I'm clear! –  Oct 18 '17 at 13:25
  • Possible duplicate of [How to update Model when binding to a ViewModel?](https://stackoverflow.com/questions/5892888/how-to-update-model-when-binding-to-a-viewmodel) – Fran Oct 18 '17 at 13:26
  • Okay, I understand your point I think. How do I convert my view model to an entity which I then pass to the data model? Wouldn't that just be an extra step of creating a new object and then pass that to the data model? Edit: I read the answers, I actually had heard of AutoMapper. But since this is a small project I think I'll stick to manual assignment. – mneumann Oct 18 '17 at 13:28
  • Sorry, it is result.InjectFrom(vm.ModifyUser) instead of result.InjectFrom(vm); I guess you figured it out already but I updated the answer – Isma Oct 18 '17 at 13:45

3 Answers3

2

One possible solution is to use AutoMapper which will automatically map properties from one object to another as long as the properties are named and typed the same. So, you may be able to do something like this depending on the property names in your class (you can also define custom maps if your properties are named differnetly):

//create the map, this is normally done in a config file but can be done in many different places
Mapper.Initialize(x =>
{
        x.CreateMap<User, type of vm.ModifyUser goes here>().ReverseMap();
}

//map the vm to the user, this will update all fields on result to what is contained in vm.ModifyUser assuming properties are named and typed the same.
....db query to retrieve result... 
result = Mapper.Map<User>(vm.ModifyUser);
context.SaveChanges()

you can add AutoMapper via the NuGet Package Manager. This is a widely used tool which is well documented.

GregH
  • 5,125
  • 8
  • 55
  • 109
2

You cannot directly set your view model object to your database entity object because they are objects of different class.

Instead, you can map each property, this can be done manually (like you are doing now), using reflection or using existing libraries such as ValueInjecter or AutoMapper.

If both objects are very similar and have the same property names, I'd use ValueInjecter as it is very fast and simple to use, AutoMapper is much slower but allows for more complex mappings (I had serious performance problems with it but choose whichever best suits your scenario, i.e. speed vs flexibility).

Here is how to do it with ValueInjecter for the example in your question:

Add the ValueInjecter NuGet:

Install-Package ValueInjecter -Version 3.1.1.5

Include the namespace in your controller class:

using Omu.ValueInjecter;

And then in your action:

[HttpPost]
public ActionResult Edit(Admin_vm vm) {
    var result = (from Users in db.Users
                              where Users.ID == usrID
                              select Users
                  ).FirstOrDefault();

    result.InjectFrom(vm.ModifyUser);

    context.SaveChanges();

    return View(vm);
}

For the Create method you need to create your object first:

User user = new User();
user.InjectFrom(vm.ModifyUser);
Isma
  • 14,604
  • 5
  • 37
  • 51
  • This is not necessarily true. He cannot use the model with the context, but he can use the entity with the view (as a model). If the entity is bound appropriately by the model binder, then you can always pass the entity back and forth and reattach it to the context and set its state to modified, then save the changes. I don't recommend it, but it's doable. – JuanR Oct 18 '17 at 13:34
  • 1
    Hola Juan. Yes, I agree it is doable but I also don't recommend it because it would be harder to maintain when the database entity changes, also you might send properties that are not intended to be displayed but can still be intercepted by monitoring the HTTP request. Also, you might send back and forth unnecessary data which will increase the bandwidth unnecessarily. – Isma Oct 18 '17 at 13:42
0

It sounds like the model binder is not working like you expect. You might need to write a custom model binder. The example below was pulled from here. This example inherits from DefaultModelBinder and then uses reflection to ONLY override the bindings when it's of the specified type (HomePageModels in the example below, but would be your ViewModel or specific ViewModel objects - i.e. Admin_vm or ModifyUser). For everything else it binds normally which is probably preferred.

public class HomeCustomDataBinder : DefaultModelBinder
    {

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType == typeof(HomePageModels))
            {
                HttpRequestBase request = controllerContext.HttpContext.Request;

                string title = request.Form.Get("Title");
                string day = request.Form.Get("Day");
                string month = request.Form.Get("Month");
                string year = request.Form.Get("Year");

                return new HomePageModels
                {
                    Title = title,
                    Date = day + "/" + month + "/" + year
                };

                //// call the default model binder this new binding context
                //return base.BindModel(controllerContext, newBindingContext);
            }
            else
            {
                return base.BindModel(controllerContext, bindingContext);
            }
        }

    } 
Gary B
  • 61
  • 5