how do View Models link to the Database? - asp.net-mvc

I am new to Asp.net MVC and could really use some clarification on how View models work.
From my understanding, View models are used to only expose necessary fields from the domain model to the Views. What I am finding hard to understand is that domain models are linked to the Db via Dbset. So it makes sense to me that when data is posted to a controller using a domain model, that this data can find its way into the Db.
From the examples of View models I have seen, they are not referenced by a Dbset. So how does data posted to a View model find its way into the database. Does EF just match the fields from the View model to fields which match from the domain model?
thanks for your help

As Jonathan stated, AutoMapper will help you map your ViewModel entities to your Domain model. Here is an example:
In your view you work with the View Model (CreateGroupVM):
#model X.X.Areas.Group.Models.CreateGroupVM
#using (Html.BeginForm(null,null, FormMethod.Post, new { #class="form-horizontal", role="form"}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
#Html.LabelFor(model => model.Title, new { #class = "col-lg-4 control-label" })
#Html.TextBoxFor(model => model.Title, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Title)
#Html.LabelFor(model => model.Description, new { #class = "col-lg-4 control-label" })
#Html.TextBoxFor(model => model.Description, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Description)
#Html.LabelFor(model => model.CategoryId, new { #class = "col-lg-4 control-label" })
#Html.DropDownListFor(x => x.CategoryId, Model.Categories)
#Html.ValidationMessageFor(model => model.CategoryId)
<div class="form-group">
<div class="col-lg-offset-4 col-lg-8">
<button type="submit" class="btn-u btn-u-blue">Create</button>
</div>
</div>
}
ViewModel (CreateGroupVM.cs):
Notice how we pass in a list of Categories - you could not do this had you strictly used your domain model because you cant pass a list of categories in the Group model. This gives us strongly typed helpers in our views, and no ViewBag usage.
public class CreateGroupVM
{
[Required]
public string Title { get; set; }
public string Description { get; set; }
[DisplayName("Category")]
public int CategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
Domain Model (Group.cs):
public class Group
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public int CreatorUserId { get; set; }
public bool Deleted { get; set; }
}
In your HttpPost Create Action - you let AutoMapper do the mapping then save to the DB. Note that by default AutoMapper will map fields that are the same name. You can read https://github.com/AutoMapper/AutoMapper/wiki/Getting-started to get started with AutoMapper.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateGroupVM vm)
{
if (ModelState.IsValid)
{
var group = new InterestGroup();
Mapper.Map(vm, group); // Let AutoMapper do the work
db.Groups.Add(group);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}

The view models are in no way tied to your database. You would need to create a new domain model and populate it with the data from the view model in order to save it to the database. Of course, having to do that is very annoying and someone created AutoMapper to handle that.
With automapper you could just match the properties from your view models to properties in the domain model and then add them to the database as needed.

Related

Best way to pass lookup values to a dropdownlist

I'm wondering what's the general approach of passing a list of lookup values to a view in MVC. Currently I have 2 db tables and I'm using db first EF6 to interface. My main table has a lookup table and I want to populate a dropdownlist of my view with all the values of the lookup so that the user can pick when creating and editing.
Employee Table
id primary key
name varchar
department id - this is the id of the department in the lookup
Department table
id primary key
name varchar
Is it best to create a partial class for the employee model and add a new property called allDepartments and then in my controller call a method that gets all the departments before passing the model to the view, or is it better to dump the departments in the viewbag/viewdata dictionary?
What is the general approach here?
You need to create a ViewModel like this:
public class EmployeeViewModel
{
public string Name { get; set; }
[Required(ErrorMessage = "...")] // to protect against under-posting attacks
[Display(Name = "Department")]
public int? DepartmentId { get; set; }
public IEnumerable<SelectListItem> Departments { get; set; }
}
Controller:
public ActionResult Create()
{
var employeeViewModel = new EmployeeViewModel();
employeeViewModel.Departments = GetDepartments().Select(option => new SelectListItem
{
Text = option.name,
Value = option.Id.ToString()
});
return View(employeeViewModel);
}
// Post
public ActionResult Create(EmployeeViewModel model)
{
// Map ViewModel to Entity and Save to db...
}
View:
#model EmployeViewModel
<div class="form-group">
#Html.LabelFor(model => model.Name)
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="form-group">
#Html.LabelFor(model => model.DepartmentId)
#Html.DropDownListFor(model => model.DepartmentId, Model.Departments, "Choose...")
#Html.ValidationMessageFor(model => model.DepartmentId)
</div>

How to save multiselected dropdownlist values into database table in Asp.net MVC

I'm using entity framework and jquery chosen plugin to get multiselected dropdownlist values for Skills property. I've struggled to make the chosen plugin work in my view, and figured it out, but now I face another problem of passing those multiselected values (ex: Skills such as Java, c#, javascript) into controller and save into my Employee table.
public IEnumerable<string> Skills { get; set; }
Above code is currently on top of my head to save multiple values, but not sure how to properly use it. Thinking about multiple ways of doing it, but I definitely need guidance.
I have a model that looks like:
public class Employee
{
public string Name { get; set; }
public string Skills { get; set; }
}
and my controller:
public ActionResult Create([Bind(Include = "Name,Skills")] Employee employee)
{
if (ModelState.IsValid)
{
db.Employees.Add(employee);
db.SaveChanges();
return View("Success");
}
return View(employee);
}
View:
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.Name)
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
#Html.LabelFor(model => model.Skills)
#Html.ListBoxFor(model => model.Skills, ViewBag.skillList as MultiSelectList,
new { #class = "chzn-select", id="skills", data_placeholder = "Choose Skills..." })
#Html.ValidationMessageFor(model => model.Skills)
<input type="submit" value="Create" class="btn btn-default" />
}
Another approach I am thinking is to create a Skill table that Employee table can have navigation property. An employee can have any number of skills, so the Skill navigation property is a collection. But honestly have little knowledge about this and need guidance for this too. For example:
public class Employee
{
public string Name { get; set; }
public int SkillID { get; set; }
public virtual ICollection<Skill> Skills { get; set; }
}
If any of these approaches don't make sense, I'd appreciate it if you can tell me why and how I can properly use it. Thanks!
I used chosen to create a filter based on multiselects, and the data model uses arrays, so in your case it would be
public string[] Skills { get; set; }

ASP.NET MVC, Using a viewmodel with strongly typed helpers

I have a question about setting up a viewmodel when you use the strongly typed helpers (HTML.EditorFor, etc.) and a viewmodel in ASP.NET MVC. I am working with MVC5, but I would imagine my question is also applicable to other versions.
For this example, I'm working with the create of the CRUD process.
In the example, the user enters the name and address of a person and city is selected from a drop down.
Here is the model:
public class Person
{
[Key]
public int PersonID { get; set; }
[ForeignKey("City")]
public int CityID { get; set; }
public string Name {get; set;}
public string address {get; set;}
//Navigational property
public virtual City City { get; set; }
}
Here is the viewmodel:
public class PersonCreateViewModel
{
public SelectList cities {get; set;}
public Person person { get; set; }
}
Here is the Action Method from the controller used to pass back the view for the create page:
public ActionResult Create()
{
CreateViewModel viewmodel = new CreateViewModel();
viewmodel.cities = new SelectList(db.Cities, "CityID", "name");
return View(viewmodel);
}
Here is part of my view:
<div class="form-group">
#Html.LabelFor(model => model.person.name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.person.name)
#Html.ValidationMessageFor(model => model.person.name)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.person.address, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.person.address)
#Html.ValidationMessageFor(model => model.person.address)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.person.CityID, "CityID", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("cities")
#Html.ValidationMessageFor(model => model.person.CityID)
</div>
</div>
I declare the model for my view as such:
#model TestProjects.ViewModels.PersonCreateViewModel
And Lastly, the http post method in the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="PersonID,CityID,nameaddress")] Person person)
{
if (ModelState.IsValid)
{
//Add to database here and return
}
//return back to view if invalid db save
return View(person);
}
So at one point I had all of this working. Then I decided I wanted to use the ViewModel approach. I still don't have it working, but here are some questions:
In the view, I reference the properties of the model with model.person.address. Is this the proper way to do this? I noticed that when it generates the html, it names the field person_address, etc.
So should I just change the Bind properties in the http post controller mehtod to reflect this? But if I change this, the properties will no longer match up with the person object causing a disconnect.
Or should I change my view model? And instead of having a person type in my ViewModel, copy/paste all of the fields from the model into the ViewModel? I guess this would also work, but is that the typical way it is done? It seems redundant to list out every property of the model when I could just have an instance if the model in the viewmodel?
In the view, I reference the properties of the model with model.person.address. Is this the proper way to do this? I noticed that when it generates the html, it names the field person_address, etc.
Yes, that is the correct way to reference model properties. More accurately, since model in your helper expressions is a reference to the Func's input parameter, it could be anything. The following would work just as well:
#Html.EditorFor(banana => banana.person.address)
So should I just change the Bind properties in the http post controller mehtod to reflect this? But if I change this, the properties will no longer match up with the person object causing a disconnect.
You don't need the bind parameters at all. What you should do is take all reference to your data entities (i.e. Person) out of your view model completely (otherwise using the view model is a little pointless as it's tightly coupled with your data entities anyway) and give the view model properties that the view needs, e.g.:
public class PersonCreateViewModel
{
public SelectList Cities { get; set; }
public string Address { get; set; }
public string Name { get; set; }
...
}
They should then bind back by default to the same model, presuming your view is correct:
public ActionResult Create (PersonCreateViewModel model)
{
// Map PersonCreateViewModel properties to Person properties
}

MVC Create page returning exception

I am having problems getting my create function to work right. I am trying to create an Order object, which has a SalesPerson and Customer object in it. My order model looks like
public class Order
{
public int ID { get; set; }
public SalesPerson SalesPerson { get; set; }
public bool PreviousWork { get; set; }
public OrderStatus Status { get; set; }
public Customer Customer { get; set; }
public List<OrderLineItem> LineItems { get; set; }
}
I then created a view model:
public class OrderViewModel
{
private sunburstdb db = new sunburstdb();
public Order originalOrder { get; set; }
public IList<SelectListItem> SalesPeopleList { get; set; }
public IList<SelectListItem> CustomersList { get; set; }
public IList<SelectListItem> OrderStatusList { get; set; }
public OrderViewModel(Order order)
{
originalOrder = order;
}
}
In my controller I have the following:
//
// GET: /Order/Create
public ActionResult Create()
{
Order order = new Order();
OrderViewModel viewModel = new OrderViewModel(order);
//IList<SelectListItem> result = new List<SelectListItem>();
viewModel.SalesPeopleList = new List<SelectListItem>();
foreach (SalesPerson person in db.SalesPeople)
{
var temp = new SelectListItem();
temp.Text = person.FullName;
temp.Value = person.ID.ToString();
viewModel.SalesPeopleList.Add(temp);
}
//viewModel.SalesPeopleList = new SelectList(result);
//result.Clear();
viewModel.CustomersList = new List<SelectListItem>();
foreach (Customer person in db.Customers)
{
var temp = new SelectListItem();
temp.Text = person.FullName;
temp.Value = person.ID.ToString();
viewModel.CustomersList.Add(temp);
}
//viewModel.CustomersList = new SelectList(result);
return View(viewModel);
}
//
// POST: /Order/Create
[HttpPost]
public ActionResult Create(Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
Finally my view is pretty standard with a couple of fields to populate the data in the order.
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Order</legend>
<div class="editor-label">
#Html.LabelFor(model => model.originalOrder.SalesPerson)
</div>
<div class="editor-field">
#Html.DropDownList("Order.SalesPerson", Model.SalesPeopleList)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.originalOrder.Customer)
</div>
<div class="editor-field">
#Html.DropDownList("Order.Customer", Model.CustomersList);
</div>
<div class="editor-label">
#Html.LabelFor(model => model.originalOrder.PreviousWork)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.originalOrder.PreviousWork)
#Html.ValidationMessageFor(model => model.originalOrder.PreviousWork)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I run this and try to create a new order I get the following: The model item passed into the dictionary is of type 'Models.Order', but this dictionary requires a model item of type 'Models.OrderViewModel'. I thought maybe I needed to change the parameter in the create method to public ActionResult Create(OrderViewModel order) however when I do this the error is: No parameterless constructor defined for this object. Can someone provide some help to an MVC Noob about what I am doing wrong?
in the action pass the viewmodel.
The error you get is because you created only a constructor with parameters, but MVC wnat also a parameterless contructor.
Aps.net 4 will create it automatically for you if you don't specify any constructor, but if you define one, then it don't take initiative creating one that maybe you don't want.
Look here. That should explain better than me
So that happens during model binding as for me.
What is the best way to debug such circumstances is to implement model binder which is inherited from default one and set it as default model binder for your object (Order).
Try to provide more information for more specific answer.
You can use the link below as a sample of custom model binder and registration
http://www.markeverard.com/blog/2011/07/18/creating-a-custom-modelbinder-allowing-validation-of-injected-composite-models/
PS: in my opinion it's not the best way to put models into viewmodels.
but there still are implementations that contain commands and services so it's up to you.

Why is my ViewModel empty on [HttpPost]? .NET MVC 3

I'm trying my hardest to use ViewModels correctly in my web application, but I'm running into various problems. One of which, is if I set a breakpoint just after I post using a Create action, my viewModel hasn't stored any of my form values. I must be doing something wrong, but I've tried a few things. Including the code below, where I name the form items the same as the viewModel fields to see if that helps.
I'm also wondering what exactly properties in your viewmodel should represent. I've seen people use different things in blog posts and whatnot.
If the view is going to render a select list, I'm under the impression the viewmodel should hold an IEnumerable SelectListItem for this as below. Yet I've seen people use IEnumerable Entity instead, to represent the type the select list represents.
Can anybody shed some light on this for me? I scrapped my entire business logic last night so I could start a fresh and try and do it correctly.
My ViewModel:
public class ServerCreateViewModel
{
public int Id { get; set; }
// CompanyName represents a field in the Company model. I did this to see if
// it would help with model binding. Beforehand it was Companies to represent the type. I've done the same for the rest of them, so I wont comment on this again.
public IEnumerable<SelectListItem> CompanyName { get; set; }
// Represents the Game model.
public IEnumerable<SelectListItem> GameTitle { get; set; }
//Represents the Location model, etc...
public IEnumerable<SelectListItem> City { get; set; }
public IEnumerable<SelectListItem> NumberOfPlayers { get; set; }
public IEnumerable<SelectListItem> CurrencyAbbreviation { get; set; }
}
My Controller action:
public ActionResult Create()
{
var viewModel = new ServerCreateViewModel();
viewModel.CompanyName = new SelectList(_dataService.Companies.All(), "Id", "CompanyName");
viewModel.GameTitle = new SelectList(_dataService.Games.All(), "Id", "GameTitle");
viewModel.City = new SelectList(_dataService.Locations.All(), "Id", "City");
viewModel.NumberOfPlayers = new SelectList(_dataService.ServerPlayers.All(), "Id", "NumberOfPlayers");
return View(viewModel);
}
[HttpPost]
public ActionResult Create(FormCollection collection, ServerCreateViewModel viewModel)
{
try
{ // I put a breakpoint in here to check the viewModel values.
// If I dont pass the viewModel into the constructor, it doesnt exist.
// When I do pass it in, its empty.
return Content("Success");
}
catch
{
return Content("Fail");
}
}
My View:
#model GameserverCompare.ViewModels.Server.ServerCreateViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Server</legend>
#Html.HiddenFor(m => m.Id)
<div class="editor-label">
#Html.LabelFor(model => model.CompanyName)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => Model.CompanyName, Model.CompanyName)
#Html.ValidationMessageFor(model => model.CompanyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GameTitle)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => Model.GameTitle, Model.GameTitle)
#Html.ValidationMessageFor(model => model.GameTitle)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.City)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => Model.City, Model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.NumberOfPlayers)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => Model.NumberOfPlayers, Model.NumberOfPlayers)
#Html.ValidationMessageFor(model => model.NumberOfPlayers)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Since you're using SelectList properties in the form model, you will need to have a different model to represent the selected values in those lists:
public class ServerCreatePostbackModel
{
public int Id { get; set; }
// CompanyName represents a field in the Company model.
public string CompanyName { get; set; }
// Represents the Game model.
public string GameTitle { get; set; }
//Represents the Location model, etc...
public string City { get; set; }
public int NumberOfPlayers { get; set; }
public string CurrencyAbbreviation { get; set; }
}
Have your HttpPost action take one of these as its argument.
Oh, and be sure to use HiddenFor for the Id property, so it gets sent back with the other data.

Resources