I've been looking into view models for mvc and I'm looking for the best way to do them. I've read loads of different articles but none seem to be clear as the "best way." So far example I might have a Customer model with the following properties:
First Name
Last Name
Title
Location
Where location is a foreign key to a location table in the database.
I want to be able to edit this customer but only the first name, last name and location. I'm not bothered about the title in the edit. So in my view I will need to pass a customer and a selected list.
Now from what I've read I have the following options (there's probably many more).
So my question is basically which is the best one?
1)
Add a select list to the ViewData["Location"] and just create a strongly typed view of customer?
2)
Create a view model where I pass a customer and select list (the data access is done in the controller):
public class ViewModelTest
{
public Customer Customer { get; set; }
public SelectList Locations { get; set; }
public ViewModelTest(Customer customer, SelectList locations)
{
Customer = customer;
Locations = locations;
}
}
3)
Create a view model where I pass a customer and list of locations and create the select list in the view model.
public class ViewModelTest
{
public Customer Customer { get; set; }
public SelectList Locations { get; set; }
public ViewModelTest(Customer customer, List<Location> locations, string selectedLocation)
{
Customer = customer;
Locations = new SelectList(locations, "LocationID", "LocationName", selectedLocation);
}
}
4)
Pass a customer and repository and do the data access in the view model.
public class ViewModelTest
{
public Customer Customer { get; set; }
public SelectList Locations { get; set; }
public ViewModelTest(Customer customer, IRepository repository, string selectedLocation)
{
Customer = customer;
Locations = new SelectList(repository.GetLocations(), "LocationID", "LocationName", selectedLocation);
}
}
5)
Create the view model with just the properties I need:
public class ViewModelTest
{
public string FirstName { get; set; }
public string LastName { get; set; }
public SelectList Locations { get; set; }
public ViewModelTest(Customer customer, SelectList locations)
{
FirstName = customer.FirstName;
LastName = customer.LastName ;
Locations = locations;
}
}
6)
Or some other combination of the above or another way.
All opinions welcome.
Here's what I may suggest: have a view model which reflects the fields of strongly typed view:
public class SomeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public IEnumerable<SelectListItem> PossibleLocations { get; set; }
}
And in your controller action populate this view model:
public ActionResult Index()
{
var customer = Repository.GetCustomer();
var locations = Repository.GetLocations();
var viewModel = new SomeViewModel
{
FirstName = customer.FirstName,
LastName = customer.LastName,
Location = customer.Location,
PossibleLocations = new SelectList(locations, "LocationID", "LocationName", customer.Location);
};
return View(viewModel);
}
[HttpPost]
public ActionResult Index(SomeViewModel viewModel)
{
// TODO: Handle the form submission
return View(viewModel);
}
Of course doing the mapping between the model and the view model manually as shown my example could become quite cumbersome and in this case I would recommend you looking at AutoMapper.
I'd have my ViewModel as this
public class SomeViewModel
{
public Customer Customer { get; set; }
public IEnumerable<Location> PossibleLocations { get; set; }
}
My controller like this:
public ActionResult Index()
{
var viewModel = new SomeViewModel
{
Customer = Repository.GetCustomer(),
PossibleLocations = Repository.GetLocations()
};
return View(viewModel);
}
and then you can access everything in your Customer object in the view like this:
Customer name - <%: Model.Customer.FirstName %> <%: Model.Customer.LastName %>
Location - <%: Html.DropDownList("LocationID", new SelectList(Model.PossibleLocations as IEnumerable, "LocationID", "LocationName", Model.Location.LocationID))%>
Related
I want to display both user info and address model in the same view.
These are my code structures:
In Data Access Layer, I have model classes:
User.cs:
public class User
{
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string RestaurantName { get; set; }
public string PrimaryPhone { get; set; }
}
Location.cs:
public class Location
{
public int Id { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string ZipCode { get; set; }
public User User { get; set; }
}
So, I have two separate repositories for user and address models which perform add and get all functions which is working perfectly fine.
Now, I want both of this model information should be displayed in the view at the same time.
How can I combine both in the same view. I'm unable to do it. I can do it, but the thing is both are displayed in separate views.
Any advice would be appreciated!
Use a ViewModel. Your ViewModel could look something like this:
public class MyViewModel
{
public User UserVm {get;set;}
public Location LocationVm {get;set;}
}
Use the MyViewModel in your view. Your controller will accept a MyViewModel object.
You can then pass the LocationVm and UserVm objects from your viewModel to your repository.
Add One more Property To your User Model like below
-- Model Section
public class User
{
public List<Location> location {get;set;}
}
-- Controller Section
public ActionResult Index()
{
List<Location> loc= new List<Location>() { new Location{ City = "one" }, new
Location{ City = "two" } };
List<User> user= new List<User>() { new User{ FirstName = "A", location =
loc }, new User{ FirstName = "B" } };
return View(user);
}
-- View Section
#model IEnumerable<YourSolutionName.Models.User>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
#{
if (item.location != null) {
foreach(var i in item.location)
{
<h1>#i.City</h1>
}
}
}
}
This is one approach by using, we can use two different models on same view.
As for demo i added static values you can add dynamic using your context object.
it may help you. Thank You
Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.
I am trying to include a list of Clients in a drop down box. I am including this list in a form (the Html.BeginForm()) so that I can pass the selected value to my POST controller. I think I am missing something, I have the following classes:
my Invoice ViewModel:
public class InvoiceViewModel
{
public InvoiceViewModel()
{
// makes sure InvoiceItems is not null after construction
InvoiceItems = new List<PrelimInvoice>();
}
public List<PrelimInvoice> InvoiceItems { get; set; }
public List<Client> ClientId { get; set; }
public Client Client { get; set; }
public decimal InvoiceTotal { get; set; }
}
My Client Model:
public class Client
{
public string ClientId { get; set; }
public string Name { get; set; }
}
My SaveInvoice method:
public ActionResult SaveInvoice()
{
var invoice = new Invoice();
TryUpdateModel(invoice);
try
{
invoice.ClientId = User.Identity.Name;
invoice.DateCreated = DateTime.Now;
//Save invoice
proent.Invoices.Add(invoice);
proent.SaveChanges();
//Process the invoice
var preliminvoice = InvoiceLogic.GetInvoice(this.HttpContext);
preliminvoice.CreateInvoice(invoice);
return RedirectToAction("Complete", new { id = invoice.InvoiceId });
}
catch
{
//Invalid - redisplay with errors
return View(invoice);
}
}
And my Index.cshtml is strongly typed to the InvoiceViewModel class.
Index.cshtml is where I generate the form.
I am not sure of the code for creating the Html.DropDownList, and whether or not I need to include a List or something of my Clients. I have dropdownlists in other places but they are strongly typed to models, not viewmodels, hence my confusion.
Can anyone assist me?
Start by adding to your ViewModel the following 2 properties:
SelectedClientId: which stores the selected value
ClientItems: stores the collection of SelectListItems which populates your drop down.
E.G.
public class ClientViewModel
{
public List<Client> Clients;
public int SelectedClientId { get; set; } // from point 1 above
public IEnumerable<SelectListItem> ClientItems // point 2 above
{
get { return new SelectList(Clients, "Id", "Name");}
}
}
Then on your View index.cshtml you would add the following:
#model ClientViewModel
#Html.DropDownListFor(m => m.SelectedClientId, Model.ClientItems)
Hope someone can help - this has been bugging me for around 2 hours - its probably something simple :)
Kendo UI Grid sends a request to my controller
http://localhost:1418/user/update?UserID=1&UserName=Admin&RoleName=Admin&Email=c.j.hannon%40gmail.com&Active=true&Company%5BCompanyID%5D=1&Company%5BCompanyName%5D=asd
However, the controller class 'Company' isnt bound by the binder? Can any one help my view model and controller action signature are below:
[HttpGet]
public JsonResult Update(UserViewModel model)
{
svcUser.UpdateUser(new UpdateUserRequest() {
UserID=model.UserID,
RoleID = model.RoleName,
Email = model.Email,
Active = model.Active.GetValueOrDefault(false),
UserName = model.UserName
});
return Json("", JsonRequestBehavior.AllowGet);
}
public class UserViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public CompanyViewModel Company { get; set; }
}
Cheers
Craig
A few things. Your immediate problem is that Company is mapped to a complex object not a primitive type. Kendo Grid just does not do this (as of this writing). Just guessing, but you probably want to setup a foreign key binding on the Grid and just pass back the Id of the company from a listbox. This is not as bad as you think and it will immediatly fix your problem and look nice too.
Maybe personal taste but seems to be a convention. Use the suffix ViewModel for the model that is bound to your View and just the suffix Model for your business objects. So a Kendo Grid is always populated with a Model.
Ex.:
public class UserModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public int CompanyID { get; set; }
}
public class CompanyModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public UserModel UserModel { get; set; }
public IList<CompanyModel> Companies { get; set; }
}
public ActionResult UserEdit(string id)
{
var model = new UserViewModel();
model.UserModel = load...
model.Companies = load list...
return View(model);
}
#model UserViewModel
...
column.ForeignKey(fk => fk.CompanyId, Model.Companies, "ID", "Name")
(Razor Notation)
BUT! This is just an example, you are better off Ajax loading the Grid with the IList becuase I assume you have many Users in the Grid at once, though you could server bind off the ViewModel with a List too. But the list of Companies is probably the same every time, so map it to the View just liek this rather than Ajax load it every time you do a row edit. (not always true)
I have a question abou view models and adding information to a database.
Let's say i have these two classes:
public class Ad {
public int Id { get; set; }
public int CategoryId { get; set; }
public string Headline { get; set; }
public string Text { get; set; }
public int Type { get; set; }
public Category Category { get; set; }
}
public class Category {
public int CategoryId { get; set; }
public int CategoryName { get; set; }
public IColletion<Ad> Ads { get; set; }
}
Context class:
public DbSet<Ad> Ads { get; set; }
public DbSet<Category> Categories { get; set; }
The models are really over simpified but i just want to get a grasp of the context. Lets say i want to create a view model for the view that are suppose to add entries to the db. How do i go about adding info to the "Ads" database table from a view model. Lets say the view model looks something like:
namespace Website.Models
{
public class CreateViewModel
{
public Ad Ad { get; set; }
public ICollection<Categories> Categories { get; set; }
public Dictionary<int, string> AdTypes { get; set; }
public CreateViewModel()
{
// to populate a dropdown on the "Create" page
this.Adtypes= new Dictionary<int, string>
{
{1, "For sale"},
{2, "Want to buy"},
{3, "Want to trade"},
{4, "Have to offer"}
};
}
}
}
The only thing i really need when adding to the db is the parameters in the Ad class (although i need the view model to render the dropdowns). But how do I extract this from the CreateViewModel to add to the db.
This is my code at the moment:
[HttpPost]
public ActionResult Create(Ad ad)
{
if (ModelState.IsValid)
{
db.Ads.Add(ad);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(ad);
Since this is expecting a Ad class, how do i extract only the Ad paramaters from the view model and insert it to the db.
Sorry, very long post and probably some serious newbie stuff. I just didn't know how to explain it better.
I would appreciate if someone could explain about view models, or direct me to some site that does.
/m
You can use Viewmodels when you need more data on the website like values for dropdowns. So lets say you want to create a car.
Car object (Car.cs)
public class Car
{
public int Id {get;set;}
public string Color {get;set;}
public string Name {get;set;}
}
But you don't want to type color by yourself in a textbox. Let's say you want to pick color from dropdown. If so you need to add somehow list (SelectList) of colors to a dropdown.
Viewmodel is helpful in this situation (CreateCarViewModel.cs)
public CreateCarViewModel
{
public Car Car {get;set;}
public SelectList Colors{ get; set; } //List of colors for dropdown
}
Controller
ActionResult CreateCar()
{
CreateCarViewModel CCVM = new CreateCarViewModel();
List<string> colors = new List<string>{"Black","White"};
CCVM.Colors = new SelectList(colors);
//Your view is expecting CreateCarViewModel object so you have to pass it
return View(CCVM);
}
CreateCar (CreateCar.cshtml)
#model YourSolutionName.ModelsFolder.CreateCarViewModel
//form etc.
{
#Html.DropDownListFor(x => x.Car.Color, Model.Colors)
#Html.TextBoxFor(x => x.Car.Name)
}
Controller Again
[HttpPost]
//Again: but now controller expects CreateCarViewModel
ActionResult CreateCar(CreateCarViewModel CCVM)
{
if (ModelState.IsValid)
//update database with CCVM.Car object and redirect to some action or whatever you want to do
else
{
//populate your colors list again
List<string> colors = new List<string>{"Black","White"};
CCVM.Colors = new SelectList(colors);
return View (CCVM);
}
}