ASP.NET 4.5, MVC 5, EF6 code first
I'm a newbie and probably asking something long-known but I couldn't find solution on the web, probably because I don't know correct terminology to formulate this question.
To simplify things, let's say I have two model classes Teacher and Kid; One kid can be assigned only to one teacher, but one teacher can have many kids. As I'm using code first, my database is constructed from these model classes:
public class Kid
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual Teacher { get; set; }
}
public class Teacher
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual ICollection<Kid> Kids { get; set; }
}
Now, I need to have a view for adding new kid with:
Textbox for Kid's name;
Dropdown with list of Teachers
So, I'm creating a data transfer object, specifically for that view:
public class AddNewKidViewDTO
{
public IEnumerable<SelectListItem> Teachers { get; set; }
public int SelectedTeacherId { get; set; }
public Kid Kid { get; set; }
}
I also have a method for populating IEnumerable Teachers:
public AddNewKidViewDTO LoadTeachersForDropDownList()
{
... //get the list of Teachers
AddNewKidViewDTO addNewKidViewDTO = new AddNewKidViewDTO();
List<SelectListItem> selectListItems = new List<SelectListItem>();
foreach (teacher in Teachers)
{
selectListItems.Add (new SelectListItem
{
Text = teacher.Name.ToString(),
Value = teacher.Id.ToString()
});
}
addNewKidViewDTO.Teachers = selectListItems;
return addNewKidViewDTO;
}
and in the view AddNewKid.cshtml
<form>
#Html.LabelFor(model => model.Kid.Name)
#Html.TextBoxFor(model => model.Kid.Name, new {id ="Name"}
<br/>
#Html.LabelFor(model => model.Kid.Teacher)
#Html.DropDownListFor(model => model.SelectedTeacherId, Model.Teachers)
</form>
Form gets submitted and in the controller I get my populated AddNewKidViewDTO model:
[HttpPost]
public ActionResult SaveNewKid (AddNewKidViewDTO addNewKidViewDTO)
{
if (ModelState.IsValid)
{
//here is where the problem comes
}
}
ModelState.IsValid in my case will always return false.
Because when it starts validating AddNewKidViewDTO.Kid, Teacher is compulsory field but in my addNewKidViewDTO model it's null. I have the necessary teacher Id contained in addNewKidViewDTO.SelectedTeacherId only.
My question is, what is an elegant way to validate my model before passing to my inner business logic methods?
Any help is appreciated.
There are multiple possible solutions:
Changing your AddNewKidViewDTO and decorating it with the DataAnnotaions for validation:
public class AddNewKidViewDTO
{
public IEnumerable<SelectListItem> Teachers { get; set; }
[Range(1, 2147483647)] //Int32 max value but you may change it
public int SelectedTeacherId { get; set; }
[Required]
public string KidName { get; set; }
}
Then you can create Kid object manually in case that your model valid.
UPDATE (to address your comment)
If you use this approach your action will look like this:
[HttpPost]
public ActionResult SaveNewKid (AddNewKidViewDTO addNewKidViewDTO)
{
if (ModelState.IsValid)
{
using (var dbContext = new yourContext())
{
var teacher = dbContext.Teachers.FirstOrDefault(t=>t.id == addNewKidViewDTO.SelectedTeacherId );
if(teacher == default(Teacher))
{
//return an error message or add validation error to model state
}
//It is also common pattern to create a factory for models
//if you have some logic involved, but in this case I simply
//want to demonstrate the approach
var kid = new Kid
{
Name = addNewKidViewDTO.KidName,
Teacher = teacher
};
dbContext.SaveChanges();
}
}
}
Write a custom model binder for AddNewKidViewDTO that will initialize Teacher property in Kid object so once you actually use Model.IsValid the property will be initialized.
Related
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.
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);
}
}
I'm trying to understand the best way to map the fields in an Edit Model (posted back from a view form) to a Domain Model so that I can SubmitChanges() and update the database record (via Linq to SQL). My example will be simple and the answer to it is probably to just do it manually. The question I'm asking is for models with many more fields where it could still be done manually and maybe that's the answer - but is there an easier way (using AutoMapper perhaps)?
My Domain Model:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int Color { get; set; }
public string SerialNumber { get; set; }
}
My View/Edit Model:
public class ProductVM
{
public int ProductId { get; set; }
public string Name { get; set; }
public IEnumberable<Color> Colors { get; set; } // To populate a dropdown in the view with color key/values
}
My Controller Action:
[HttpPost]
public ActionResult UpdateProduct(ProductVM product)
{
var productService = new ProductService();
productService.Update(product);
}
The Update method in my service layer:
public void Update(ProductVM productVM)
{
Product product = dataContext.Products.Single(p=>p.ProductId == productVM.ProductId);
// What's the best way to map the fields of productVM into product so I can submit the changes?
dataContext.SubmitChanges();
}
I just want to map the fields from productVM that match up with the fields in product. The Edit Model has fields that don't exist in the Domain Model and the Domain Model has fields that don't exist in the Edit Model. My ultimate goal is to update the fields in the database from the fields in the Edit Model.
Thanks
Use Automapper
public void Update(ProductVM productVM)
{
Product product = dataContext.Products.Single(p=>p.ProductId == productVM.ProductId);
AutoMapper.Mapper.DynamicMap<ProductVM, Product >(productVM, product );
dataContext.SubmitChanges();
}
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))%>